mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
Compare commits
49 commits
master
...
v3-alpha-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d14e4048aa | ||
|
|
7fd627f169 |
||
|
|
b91468b6f2 | ||
|
|
0b9cd4be5d |
||
|
|
4c587ee1b8 |
||
|
|
19e1e8b8a6 |
||
|
|
67e9522c67 |
||
|
|
a829b38a34 |
||
|
|
c8dae94b5b |
||
|
|
87267758ac | ||
|
|
5f72df81fb | ||
|
|
1ed270fe05 |
||
|
|
19a654a2b1 |
||
|
|
f9bbc11711 |
||
|
|
42b1807c36 |
||
|
|
647982de1a |
||
|
|
f5557c612a |
||
|
|
f4749db8b3 | ||
|
|
86a1de6788 | ||
|
|
792c5e2d95 | ||
|
|
6758580be9 |
||
|
|
6f246eed4a | ||
|
|
fc3725d3f4 | ||
|
|
00c6f0dfdb |
||
|
|
cf7b4e2458 |
||
|
|
f682e44367 |
||
|
|
9d1f86c410 |
||
|
|
dac281ac32 |
||
|
|
29a58086a3 |
||
|
|
cb8eb755a7 |
||
|
|
1e8fc29ee4 |
||
|
|
9a05b49e3d |
||
|
|
79f8d92084 |
||
|
|
6e56542586 |
||
|
|
c53443b62b |
||
|
|
1128662c89 |
||
|
|
676787417f |
||
|
|
57422dccf3 |
||
|
|
7f3f51e36b |
||
|
|
ef184ec8bf | ||
|
|
9bfe3094dd |
||
|
|
3547b4d010 |
||
|
|
1222e3aa1b |
||
|
|
cff3ee5079 |
||
|
|
c91aa462aa |
||
|
|
62b3775e2f |
||
|
|
54bf8c1142 |
||
|
|
c4f613e4c5 |
||
|
|
a66d9ab0b1 |
152 changed files with 13154 additions and 529 deletions
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/wailsapp/wails/v2/cmd/wails/flags"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
|
||||
|
||||
|
|
@ -36,6 +37,10 @@ import (
|
|||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
)
|
||||
|
||||
const (
|
||||
viteMinVersion = "v3.0.0"
|
||||
)
|
||||
|
||||
func sliceToMap(input []string) map[string]struct{} {
|
||||
result := map[string]struct{}{}
|
||||
for _, value := range input {
|
||||
|
|
@ -88,10 +93,11 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
|||
buildOptions.IgnoreApplication = false
|
||||
}
|
||||
|
||||
legacyUseDevServerInsteadofCustomScheme := false
|
||||
// frontend:dev:watcher command.
|
||||
frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery()
|
||||
if command := projectConfig.DevWatcherCommand; command != "" {
|
||||
closer, devServerURL, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
|
||||
closer, devServerURL, devServerViteVersion, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -100,6 +106,12 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
|||
f.FrontendDevServerURL = devServerURL
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if devServerViteVersion != "" && semver.Compare(devServerViteVersion, viteMinVersion) < 0 {
|
||||
logutils.LogRed("Please upgrade your Vite Server to at least '%s' future Wails versions will require at least Vite '%s'", viteMinVersion, viteMinVersion)
|
||||
time.Sleep(3 * time.Second)
|
||||
legacyUseDevServerInsteadofCustomScheme = true
|
||||
}
|
||||
} else if frontendDevAutoDiscovery {
|
||||
return fmt.Errorf("unable to auto discover frontend:dev:serverUrl without a frontend:dev:watcher command, please either set frontend:dev:watcher or remove the auto discovery from frontend:dev:serverUrl")
|
||||
}
|
||||
|
|
@ -107,7 +119,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
|||
// Do initial build but only for the application.
|
||||
logger.Println("Building application for development...")
|
||||
buildOptions.IgnoreFrontend = true
|
||||
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel)
|
||||
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
|
||||
buildOptions.IgnoreFrontend = ignoreFrontend || f.FrontendDevServerURL != ""
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -153,7 +165,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
|||
}()
|
||||
|
||||
// Watch for changes and trigger restartApp()
|
||||
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL())
|
||||
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL(), legacyUseDevServerInsteadofCustomScheme)
|
||||
|
||||
// Kill the current program if running and remove dev binary
|
||||
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
|
||||
|
|
@ -202,7 +214,7 @@ func runCommand(dir string, exitOnError bool, command string, args ...string) er
|
|||
}
|
||||
|
||||
// runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev`
|
||||
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, error) {
|
||||
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, string, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
scanner := NewStdoutScanner()
|
||||
cmdSlice := strings.Split(devCommand, " ")
|
||||
|
|
@ -214,7 +226,7 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
|||
|
||||
if err := cmd.Start(); err != nil {
|
||||
cancel()
|
||||
return nil, "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
|
||||
return nil, "", "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
|
||||
}
|
||||
|
||||
var viteServerURL string
|
||||
|
|
@ -224,10 +236,19 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
|||
viteServerURL = serverURL
|
||||
case <-time.After(time.Second * 10):
|
||||
cancel()
|
||||
return nil, "", errors.New("failed to find Vite server URL")
|
||||
return nil, "", "", errors.New("failed to find Vite server URL")
|
||||
}
|
||||
}
|
||||
|
||||
viteVersion := ""
|
||||
select {
|
||||
case version := <-scanner.ViteServerVersionC:
|
||||
viteVersion = version
|
||||
|
||||
case <-time.After(time.Second * 5):
|
||||
// That's fine, then most probably it was not vite that was running
|
||||
}
|
||||
|
||||
logutils.LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
|
@ -255,11 +276,11 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
|||
}
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}, viteServerURL, nil
|
||||
}, viteServerURL, viteVersion, nil
|
||||
}
|
||||
|
||||
// restartApp does the actual rebuilding of the application when files change
|
||||
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int) (*process.Process, string, error) {
|
||||
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int, legacyUseDevServerInsteadofCustomScheme bool) (*process.Process, string, error) {
|
||||
|
||||
appBinary, err := build.Build(buildOptions)
|
||||
println()
|
||||
|
|
@ -297,6 +318,9 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
|
|||
os.Setenv("assetdir", f.AssetDir)
|
||||
os.Setenv("devserver", f.DevServer)
|
||||
os.Setenv("frontenddevserverurl", f.FrontendDevServerURL)
|
||||
if legacyUseDevServerInsteadofCustomScheme {
|
||||
os.Setenv("legacyusedevsererinsteadofcustomscheme", "true")
|
||||
}
|
||||
|
||||
// Start up new binary with correct args
|
||||
newProcess := process.NewProcess(appBinary, args...)
|
||||
|
|
@ -316,7 +340,7 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
|
|||
}
|
||||
|
||||
// doWatcherLoop is the main watch loop that runs while dev is active
|
||||
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) *process.Process {
|
||||
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL, legacyUseDevServerInsteadofCustomScheme bool) *process.Process {
|
||||
// Main Loop
|
||||
var extensionsThatTriggerARebuild = sliceToMap(strings.Split(f.Extensions, ","))
|
||||
var dirsThatTriggerAReload []string
|
||||
|
|
@ -422,7 +446,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
|
|||
rebuild = false
|
||||
logutils.LogGreen("[Rebuild triggered] files updated")
|
||||
// Try and build the app
|
||||
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel)
|
||||
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
|
||||
if err != nil {
|
||||
logutils.LogRed("Error during build: %s", err.Error())
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -2,30 +2,47 @@ package dev
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// stdoutScanner acts as a stdout target that will scan the incoming
|
||||
// data to find out the vite server url
|
||||
type stdoutScanner struct {
|
||||
ViteServerURLChan chan string
|
||||
ViteServerURLChan chan string
|
||||
ViteServerVersionC chan string
|
||||
versionDetected bool
|
||||
}
|
||||
|
||||
// NewStdoutScanner creates a new stdoutScanner
|
||||
func NewStdoutScanner() *stdoutScanner {
|
||||
return &stdoutScanner{
|
||||
ViteServerURLChan: make(chan string, 2),
|
||||
ViteServerURLChan: make(chan string, 2),
|
||||
ViteServerVersionC: make(chan string, 2),
|
||||
}
|
||||
}
|
||||
|
||||
// Write bytes to the scanner. Will copy the bytes to stdout
|
||||
func (s *stdoutScanner) Write(data []byte) (n int, err error) {
|
||||
input := stripansi.Strip(string(data))
|
||||
if !s.versionDetected {
|
||||
v, err := detectViteVersion(input)
|
||||
if v != "" || err != nil {
|
||||
if err != nil {
|
||||
logutils.LogRed("ViteStdoutScanner: %s", err)
|
||||
v = "v0.0.0"
|
||||
}
|
||||
s.ViteServerVersionC <- v
|
||||
s.versionDetected = true
|
||||
}
|
||||
}
|
||||
|
||||
match := strings.Index(input, "Local:")
|
||||
if match != -1 {
|
||||
sc := bufio.NewScanner(strings.NewReader(input))
|
||||
|
|
@ -47,3 +64,21 @@ func (s *stdoutScanner) Write(data []byte) (n int, err error) {
|
|||
}
|
||||
return os.Stdout.Write(data)
|
||||
}
|
||||
|
||||
func detectViteVersion(line string) (string, error) {
|
||||
s := strings.Split(strings.TrimSpace(line), " ")
|
||||
if strings.ToLower(s[0]) != "vite" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(line) < 2 {
|
||||
return "", fmt.Errorf("unable to parse vite version")
|
||||
}
|
||||
|
||||
v := s[1]
|
||||
if !semver.IsValid(v) {
|
||||
return "", fmt.Errorf("%s is not a valid vite version string", v)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^2.9.9"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,11 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
iofs "io/fs"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
|
||||
|
|
@ -104,17 +106,35 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
|||
}
|
||||
|
||||
if frontendDevServerURL != "" {
|
||||
if devServer == "" {
|
||||
return nil, fmt.Errorf("Unable to use FrontendDevServerUrl without a DevServer address")
|
||||
if os.Getenv("legacyusedevsererinsteadofcustomscheme") != "" {
|
||||
startURL, err := url.Parse("http://" + devServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, "starturl", startURL)
|
||||
}
|
||||
|
||||
startURL, err := url.Parse("http://" + devServer)
|
||||
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
|
||||
|
||||
externalURL, err := url.Parse(frontendDevServerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, "starturl", startURL)
|
||||
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
|
||||
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 {
|
||||
|
|
@ -246,3 +266,22 @@ func tryInferAssetDirFromFS(assets iofs.FS) (string, error) {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,5 +12,6 @@ typedef int Role;
|
|||
|
||||
static const Role AppMenu = 1;
|
||||
static const Role EditMenu = 2;
|
||||
static const Role WindowMenu = 3;
|
||||
|
||||
#endif /* Role_h */
|
||||
|
|
|
|||
|
|
@ -68,12 +68,20 @@
|
|||
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;
|
||||
if (ctx.aboutTitle != nil) {
|
||||
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
|
||||
}
|
||||
[appMenu addItem:quitMenuItem];
|
||||
[self appendSubmenu:appMenu];
|
||||
break;
|
||||
|
|
@ -100,6 +108,17 @@
|
|||
[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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@ func (f *Frontend) startMessageProcessor() {
|
|||
func (f *Frontend) startRequestProcessor() {
|
||||
for request := range requestBuffer {
|
||||
f.assets.ServeWebViewRequest(request)
|
||||
request.Release()
|
||||
}
|
||||
}
|
||||
func (f *Frontend) startCallbackProcessor() {
|
||||
|
|
|
|||
|
|
@ -466,7 +466,6 @@ var requestBuffer = make(chan webview.Request, 100)
|
|||
func (f *Frontend) startRequestProcessor() {
|
||||
for request := range requestBuffer {
|
||||
f.assets.ServeWebViewRequest(request)
|
||||
request.Release()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,11 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
|
||||
|
|
@ -67,7 +65,6 @@ func (d *DevWebServer) Run(ctx context.Context) error {
|
|||
myLogger = _logger.(*logger.Logger)
|
||||
}
|
||||
|
||||
var assetHandler http.Handler
|
||||
var wsHandler http.Handler
|
||||
|
||||
_fronendDevServerURL, _ := ctx.Value("frontenddevserverurl").(string)
|
||||
|
|
@ -77,33 +74,23 @@ func (d *DevWebServer) Run(ctx context.Context) error {
|
|||
return c.String(http.StatusOK, assetdir)
|
||||
})
|
||||
|
||||
var err error
|
||||
assetHandler, err = assetserver.NewAssetHandler(assetServerConfig, myLogger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
externalURL, err := url.Parse(_fronendDevServerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if externalURL.Host == "" {
|
||||
return fmt.Errorf("Invalid frontend:dev:serverUrl missing protocol scheme?")
|
||||
}
|
||||
|
||||
waitCb := func() { d.LogDebug("Waiting for frontend DevServer '%s' to be ready", externalURL) }
|
||||
if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) {
|
||||
d.logger.Error("Timeout waiting for frontend DevServer")
|
||||
}
|
||||
|
||||
assetHandler = newExternalDevServerAssetHandler(d.logger, externalURL, assetServerConfig)
|
||||
// WebSockets aren't currently supported in prod mode, so a WebSocket connection is the result of the
|
||||
// FrontendDevServer e.g. Vite to support auto reloads.
|
||||
// Therefore we direct WebSockets directly to the FrontendDevServer instead of returning a NotImplementedStatus.
|
||||
wsHandler = httputil.NewSingleHostReverseProxy(externalURL)
|
||||
}
|
||||
|
||||
assetHandler, err := assetserver.NewAssetHandler(assetServerConfig, myLogger)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Setup internal dev server
|
||||
bindingsJSON, err := d.appBindings.ToJSON()
|
||||
if err != nil {
|
||||
|
|
@ -307,22 +294,3 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
|||
result.server.HidePort = true
|
||||
return result
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//go:build dev
|
||||
// +build dev
|
||||
|
||||
package devserver
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
|
@ -10,21 +10,12 @@ import (
|
|||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
)
|
||||
|
||||
func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, options assetserver.Options) http.Handler {
|
||||
handler := newExternalAssetsHandler(logger, url, options.Handler)
|
||||
func NewExternalAssetsHandler(logger Logger, options assetserver.Options, url *url.URL) http.Handler {
|
||||
baseHandler := options.Handler
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.Handler) http.Handler {
|
||||
errSkipProxy := fmt.Errorf("skip proxying")
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
|
|
@ -37,7 +28,7 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
|||
}
|
||||
|
||||
proxy.ModifyResponse = func(res *http.Response) error {
|
||||
if handler == nil {
|
||||
if baseHandler == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -53,11 +44,11 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
|||
}
|
||||
|
||||
proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) {
|
||||
if handler != nil && errors.Is(err, errSkipProxy) {
|
||||
if baseHandler != nil && errors.Is(err, errSkipProxy) {
|
||||
if logger != nil {
|
||||
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using AssetHandler", r.URL)
|
||||
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL)
|
||||
}
|
||||
handler.ServeHTTP(rw, r)
|
||||
baseHandler.ServeHTTP(rw, r)
|
||||
} else {
|
||||
if logger != nil {
|
||||
logger.Error("[ExternalAssetHandler] Proxy error: %v", err)
|
||||
|
|
@ -66,18 +57,24 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
|||
}
|
||||
}
|
||||
|
||||
return http.HandlerFunc(
|
||||
var result http.Handler = http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method == http.MethodGet {
|
||||
proxy.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if handler != nil {
|
||||
handler.ServeHTTP(rw, req)
|
||||
if baseHandler != nil {
|
||||
baseHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
||||
})
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
result = middleware(result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -56,13 +56,7 @@ func (r legacyRequest) Response() webview.ResponseWriter {
|
|||
return &legacyRequestNoOpCloserResponseWriter{r.rw}
|
||||
}
|
||||
|
||||
func (r legacyRequest) AddRef() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r legacyRequest) Release() error {
|
||||
return nil
|
||||
}
|
||||
func (r legacyRequest) Close() error { return nil }
|
||||
|
||||
func (r *legacyRequest) request() (*http.Request, error) {
|
||||
if r.req != nil {
|
||||
|
|
@ -81,6 +75,4 @@ type legacyRequestNoOpCloserResponseWriter struct {
|
|||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (*legacyRequestNoOpCloserResponseWriter) Finish() error {
|
||||
return nil
|
||||
}
|
||||
func (*legacyRequestNoOpCloserResponseWriter) Finish() {}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ type assetServerWebView struct {
|
|||
|
||||
// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server.
|
||||
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
||||
// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way.
|
||||
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
d.dispatchInit.Do(func() {
|
||||
workers := d.dispatchWorkers
|
||||
|
|
@ -33,8 +34,11 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
|||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
for req := range workerC {
|
||||
uri, _ := req.URL()
|
||||
d.processWebViewRequest(req)
|
||||
req.Release()
|
||||
if err := req.Close(); err != nil {
|
||||
d.logError("Unable to call close for request for uri '%s'", uri)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
@ -45,12 +49,6 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
|||
d.dispatchReqC = dispatchC
|
||||
})
|
||||
|
||||
if err := req.AddRef(); err != nil {
|
||||
uri, _ := req.URL()
|
||||
d.logError("Unable to call AddRef for request '%s'", uri)
|
||||
return
|
||||
}
|
||||
|
||||
d.dispatchReqC <- req
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,5 @@ type Request interface {
|
|||
|
||||
Response() ResponseWriter
|
||||
|
||||
AddRef() error
|
||||
Release() error
|
||||
Close() error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,11 +118,9 @@ import (
|
|||
)
|
||||
|
||||
// NewRequest creates as new WebViewRequest based on a pointer to an `id<WKURLSchemeTask>`
|
||||
//
|
||||
// Please make sure to call Release() when finished using the request.
|
||||
func NewRequest(wkURLSchemeTask unsafe.Pointer) Request {
|
||||
C.URLSchemeTaskRetain(wkURLSchemeTask)
|
||||
return &request{task: wkURLSchemeTask}
|
||||
return newRequestFinalizer(&request{task: wkURLSchemeTask})
|
||||
}
|
||||
|
||||
var _ Request = &request{}
|
||||
|
|
@ -135,16 +133,6 @@ type request struct {
|
|||
rw *responseWriter
|
||||
}
|
||||
|
||||
func (r *request) AddRef() error {
|
||||
C.URLSchemeTaskRetain(r.task)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) Release() error {
|
||||
C.URLSchemeTaskRelease(r.task)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) URL() (string, error) {
|
||||
return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil
|
||||
}
|
||||
|
|
@ -205,6 +193,16 @@ func (r *request) Response() ResponseWriter {
|
|||
return r.rw
|
||||
}
|
||||
|
||||
func (r *request) Close() error {
|
||||
var err error
|
||||
if r.body != nil {
|
||||
err = r.body.Close()
|
||||
}
|
||||
r.Response().Finish()
|
||||
C.URLSchemeTaskRelease(r.task)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = &requestBodyStreamReader{}
|
||||
|
||||
type requestBodyStreamReader struct {
|
||||
|
|
|
|||
40
v2/pkg/assetserver/webview/request_finalizer.go
Normal file
40
v2/pkg/assetserver/webview/request_finalizer.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package webview
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var _ Request = &requestFinalizer{}
|
||||
|
||||
type requestFinalizer struct {
|
||||
Request
|
||||
closed int32
|
||||
}
|
||||
|
||||
// newRequestFinalizer returns a request with a runtime finalizer to make sure it will be closed from the finalizer
|
||||
// if it has not been already closed.
|
||||
// It also makes sure Close() of the wrapping request is only called once.
|
||||
func newRequestFinalizer(r Request) Request {
|
||||
rf := &requestFinalizer{Request: r}
|
||||
// Make sure to async release since it might block the finalizer goroutine for a longer period
|
||||
runtime.SetFinalizer(rf, func(obj *requestFinalizer) { rf.close(true) })
|
||||
return rf
|
||||
}
|
||||
|
||||
func (r *requestFinalizer) Close() error {
|
||||
return r.close(false)
|
||||
}
|
||||
|
||||
func (r *requestFinalizer) close(asyncRelease bool) error {
|
||||
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
|
||||
runtime.SetFinalizer(r, nil)
|
||||
if asyncRelease {
|
||||
go r.Request.Close()
|
||||
return nil
|
||||
} else {
|
||||
return r.Request.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -18,13 +18,12 @@ import (
|
|||
)
|
||||
|
||||
// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest`
|
||||
//
|
||||
// Please make sure to call Release() when finished using the request.
|
||||
func NewRequest(webKitURISchemeRequest unsafe.Pointer) Request {
|
||||
webkitReq := (*C.WebKitURISchemeRequest)(webKitURISchemeRequest)
|
||||
C.g_object_ref(C.gpointer(webkitReq))
|
||||
|
||||
req := &request{req: webkitReq}
|
||||
req.AddRef()
|
||||
return req
|
||||
return newRequestFinalizer(req)
|
||||
}
|
||||
|
||||
var _ Request = &request{}
|
||||
|
|
@ -37,16 +36,6 @@ type request struct {
|
|||
rw *responseWriter
|
||||
}
|
||||
|
||||
func (r *request) AddRef() error {
|
||||
C.g_object_ref(C.gpointer(r.req))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) Release() error {
|
||||
C.g_object_unref(C.gpointer(r.req))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) URL() (string, error) {
|
||||
return C.GoString(C.webkit_uri_scheme_request_get_uri(r.req)), nil
|
||||
}
|
||||
|
|
@ -82,3 +71,13 @@ func (r *request) Response() ResponseWriter {
|
|||
r.rw = &responseWriter{req: r.req}
|
||||
return r.rw
|
||||
}
|
||||
|
||||
func (r *request) Close() error {
|
||||
var err error
|
||||
if r.body != nil {
|
||||
err = r.body.Close()
|
||||
}
|
||||
r.Response().Finish()
|
||||
C.g_object_unref(C.gpointer(r.req))
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@ var (
|
|||
type ResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
|
||||
// Finish the response and flush all data.
|
||||
Finish() error
|
||||
// Finish the response and flush all data. A Finish after the request has already been finished has no effect.
|
||||
Finish()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,16 +133,15 @@ func (rw *responseWriter) WriteHeader(code int) {
|
|||
C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen))
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Finish() error {
|
||||
func (rw *responseWriter) Finish() {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
rw.finished = true
|
||||
|
||||
C.URLSchemeTaskDidFinish(rw.r.task)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,19 +84,18 @@ func (rw *responseWriter) WriteHeader(code int) {
|
|||
}
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Finish() error {
|
||||
func (rw *responseWriter) Finish() {
|
||||
if !rw.wroteHeader {
|
||||
rw.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if rw.finished {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
rw.finished = true
|
||||
if rw.w != nil {
|
||||
rw.w.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rw *responseWriter) finishWithError(code int, err error) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ type Role int
|
|||
|
||||
// These constants need to be kept in sync with `v2/internal/frontend/desktop/darwin/Role.h`
|
||||
const (
|
||||
AppMenuRole Role = 1
|
||||
EditMenuRole = 2
|
||||
AppMenuRole Role = 1
|
||||
EditMenuRole = 2
|
||||
WindowMenuRole = 3
|
||||
//AboutRole Role = "about"
|
||||
//UndoRole Role = "undo"
|
||||
//RedoRole Role = "redo"
|
||||
|
|
@ -142,14 +143,16 @@ func ViewMenu() *MenuItem {
|
|||
Role: ViewMenuRole,
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// WindowMenu provides a MenuItem with the whole default "Window" menu (Minimize, Zoom, etc.).
|
||||
// On MacOS currently all options in there won't work if the window is frameless.
|
||||
func WindowMenu() *MenuItem {
|
||||
return &MenuItem{
|
||||
Role: WindowMenuRole,
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// These roles are Mac only
|
||||
|
||||
// AppMenu provides a MenuItem with the whole default "App" menu (About, Services, etc.)
|
||||
|
|
|
|||
|
|
@ -160,10 +160,14 @@ func processMenus(appoptions *App) {
|
|||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
if appoptions.Menu == nil {
|
||||
appoptions.Menu = menu.NewMenuFromItems(
|
||||
menu.AppMenu(),
|
||||
items := []*menu.MenuItem{
|
||||
menu.EditMenu(),
|
||||
)
|
||||
}
|
||||
if !appoptions.Frameless {
|
||||
items = append(items, menu.WindowMenu()) // Current options in Window Menu only work if not frameless
|
||||
}
|
||||
|
||||
appoptions.Menu = menu.NewMenuFromItems(menu.AppMenu(), items...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,6 @@
|
|||
"svelte-preprocess": "^4.10.7",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.0.0"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,6 @@
|
|||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"svelte": "^3.49.0",
|
||||
"vite": "^3.0.0"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.5.4",
|
||||
"vite": "^2.9.9"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,6 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^2.9.9"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
||||
319
v3/STATUS.md
Normal file
319
v3/STATUS.md
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
# Status
|
||||
|
||||
Status of features in v3. Incomplete - please add as you see fit.
|
||||
|
||||
## Application
|
||||
|
||||
Application interface methods
|
||||
|
||||
| Method | Windows | Linux | Mac | Notes |
|
||||
|---------------------------------------------------------------|---------|-------|-----|-------|
|
||||
| run() error | | | Y | |
|
||||
| destroy() | | | Y | |
|
||||
| setApplicationMenu(menu *Menu) | | | Y | |
|
||||
| name() string | | | Y | |
|
||||
| getCurrentWindowID() uint | | | Y | |
|
||||
| showAboutDialog(name string, description string, icon []byte) | | | Y | |
|
||||
| setIcon(icon []byte) | | | Y | |
|
||||
| on(id uint) | | | Y | |
|
||||
| dispatchOnMainThread(fn func()) | Y | | Y | |
|
||||
| hide() | Y | | Y | |
|
||||
| show() | Y | | Y | |
|
||||
| getPrimaryScreen() (*Screen, error) | | | Y | |
|
||||
| getScreens() ([]*Screen, error) | | | Y | |
|
||||
|
||||
## Webview Window
|
||||
|
||||
Webview Window Interface Methods
|
||||
|
||||
| Method | Windows | Linux | Mac | Notes |
|
||||
|----------------------------------------------------|---------|-------|-----|------------------------------------------|
|
||||
| center() | Y | | Y | |
|
||||
| close() | | | Y | |
|
||||
| destroy() | | | Y | |
|
||||
| execJS(js string) | | | Y | |
|
||||
| focus() | Y | | | |
|
||||
| forceReload() | | | Y | |
|
||||
| fullscreen() | Y | | Y | |
|
||||
| getScreen() (*Screen, error) | | | Y | |
|
||||
| getZoom() float64 | | | Y | |
|
||||
| height() int | Y | | Y | |
|
||||
| hide() | Y | | Y | |
|
||||
| isFullscreen() bool | Y | | Y | |
|
||||
| isMaximised() bool | Y | | Y | |
|
||||
| isMinimised() bool | Y | | Y | |
|
||||
| maximise() | Y | | Y | |
|
||||
| minimise() | Y | | Y | |
|
||||
| nativeWindowHandle() (uintptr, error) | Y | | | |
|
||||
| on(eventID uint) | | | Y | |
|
||||
| openContextMenu(menu *Menu, data *ContextMenuData) | | | Y | |
|
||||
| position() (int, int) | Y | | Y | |
|
||||
| reload() | | | Y | |
|
||||
| run() | Y | | Y | |
|
||||
| setAlwaysOnTop(alwaysOnTop bool) | Y | | Y | |
|
||||
| setBackgroundColour(color RGBA) | Y | | Y | |
|
||||
| setFrameless(bool) | | | Y | |
|
||||
| setFullscreenButtonEnabled(enabled bool) | - | | Y | There is no fullscreen button in Windows |
|
||||
| setHTML(html string) | | | Y | |
|
||||
| setMaxSize(width, height int) | Y | | Y | |
|
||||
| setMinSize(width, height int) | Y | | Y | |
|
||||
| setPosition(x int, y int) | Y | | Y | |
|
||||
| setResizable(resizable bool) | Y | | Y | |
|
||||
| setSize(width, height int) | Y | | Y | |
|
||||
| setTitle(title string) | Y | | Y | |
|
||||
| setURL(url string) | | | Y | |
|
||||
| setZoom(zoom float64) | | | Y | |
|
||||
| show() | Y | | Y | |
|
||||
| size() (int, int) | Y | | Y | |
|
||||
| toggleDevTools() | | | Y | |
|
||||
| unfullscreen() | Y | | Y | |
|
||||
| unmaximise() | Y | | Y | |
|
||||
| unminimise() | Y | | Y | |
|
||||
| width() int | Y | | Y | |
|
||||
| zoom() | | | Y | |
|
||||
| zoomIn() | | | Y | |
|
||||
| zoomOut() | | | Y | |
|
||||
| zoomReset() | | | Y | |
|
||||
|
||||
## Runtime
|
||||
|
||||
### Application
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------|---------|-------|-----|-------|
|
||||
| Quit | | | Y | |
|
||||
| Hide | Y | | Y | |
|
||||
| Show | Y | | Y | |
|
||||
|
||||
### Dialogs
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|----------|---------|-------|-----|-------|
|
||||
| Info | | | Y | |
|
||||
| Warning | | | Y | |
|
||||
| Error | | | Y | |
|
||||
| Question | | | Y | |
|
||||
| OpenFile | | | Y | |
|
||||
| SaveFile | | | Y | |
|
||||
|
||||
### Clipboard
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------|---------|-------|-----|-------|
|
||||
| SetText | | | Y | |
|
||||
| Text | | | Y | |
|
||||
|
||||
### ContextMenu
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|-----------------|---------|-------|-----|-------|
|
||||
| OpenContextMenu | | | Y | |
|
||||
|
||||
### Screens
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|------------|---------|-------|-----|-------|
|
||||
| GetAll | Y | | Y | |
|
||||
| GetPrimary | | | Y | |
|
||||
| GetCurrent | | | Y | |
|
||||
|
||||
### Window
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------------------|---------|-------|-----|--------------------------------------------------------------------------------------|
|
||||
| SetTitle | Y | | Y | |
|
||||
| SetSize | Y | | Y | |
|
||||
| Size | Y | | Y | |
|
||||
| SetPosition | Y | | Y | |
|
||||
| Position | Y | | Y | |
|
||||
| Focus | Y | | | |
|
||||
| FullScreen | Y | | Y | |
|
||||
| UnFullscreen | Y | | Y | |
|
||||
| Minimise | Y | | Y | |
|
||||
| UnMinimise | Y | | Y | |
|
||||
| Maximise | Y | | Y | |
|
||||
| UnMaximise | Y | | Y | |
|
||||
| Show | Y | | Y | |
|
||||
| Hide | Y | | Y | |
|
||||
| Center | Y | | Y | |
|
||||
| SetBackgroundColour | Y | | Y | https://github.com/MicrosoftEdge/WebView2Feedback/issues/1621#issuecomment-938234294 |
|
||||
| SetAlwaysOnTop | Y | | Y | |
|
||||
| SetResizable | Y | | Y | |
|
||||
| SetMinSize | Y | | Y | |
|
||||
| SetMaxSize | Y | | Y | |
|
||||
| Width | Y | | Y | |
|
||||
| Height | Y | | Y | |
|
||||
| ZoomIn | | | Y | Increase view scale |
|
||||
| ZoomOut | | | Y | Decrease view scale |
|
||||
| ZoomReset | | | Y | Reset view scale |
|
||||
| GetZoom | | | Y | Get current view scale |
|
||||
| SetZoom | | | Y | Set view scale |
|
||||
| Screen | | | Y | Get screen for window |
|
||||
|
||||
### Window Options
|
||||
|
||||
A 'Y' in the table below indicates that the option has been tested and is applied when the window is created.
|
||||
An 'X' indicates that the option is not supported by the platform.
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------------------------------|---------|-------|-----|--------------------------------------------|
|
||||
| Name | | | | |
|
||||
| Title | Y | | | |
|
||||
| Width | Y | | | |
|
||||
| Height | Y | | | |
|
||||
| AlwaysOnTop | Y | | | |
|
||||
| URL | | | | |
|
||||
| DisableResize | Y | | | |
|
||||
| Frameless | | | | |
|
||||
| MinWidth | Y | | | |
|
||||
| MinHeight | Y | | | |
|
||||
| MaxWidth | Y | | | |
|
||||
| MaxHeight | Y | | | |
|
||||
| StartState | Y | | | |
|
||||
| Mac | - | - | | |
|
||||
| BackgroundType | | | | Acrylic seems to work but the others don't |
|
||||
| BackgroundColour | Y | | | |
|
||||
| HTML | | | | |
|
||||
| JS | | | | |
|
||||
| CSS | | | | |
|
||||
| X | | | | |
|
||||
| Y | | | | |
|
||||
| HideOnClose | | | | |
|
||||
| FullscreenButtonEnabled | | | | |
|
||||
| Hidden | Y | | | |
|
||||
| EnableFraudulentWebsiteWarnings | | | | |
|
||||
| Zoom | | | | |
|
||||
| EnableDragAndDrop | | | | |
|
||||
| Windows | Y | - | - | |
|
||||
| Focused | Y | | | |
|
||||
|
||||
### Log
|
||||
|
||||
To log or not to log? System logger vs custom logger.
|
||||
|
||||
## Menu
|
||||
|
||||
| Event | Windows | Linux | Mac | Notes |
|
||||
|--------------------------|---------|-------|-----|-------|
|
||||
| Default Application Menu | | | Y | |
|
||||
|
||||
## Tray Menus
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|--------------------|---------|-------|-----|----------------------------------------------------------------------|
|
||||
| Icon | Y | | Y | Windows has default icons for light/dark mode & supports PNG or ICO. |
|
||||
| Label | - | | Y | |
|
||||
| Label (ANSI Codes) | - | | | |
|
||||
| Menu | | | Y | |
|
||||
|
||||
## Cross Platform Events
|
||||
|
||||
Mapping native events to cross-platform events.
|
||||
|
||||
| Event | Windows | Linux | Mac | Notes |
|
||||
|--------------------------|---------|-------|-----------------|-------|
|
||||
| WindowWillClose | | | WindowWillClose | |
|
||||
| WindowDidClose | | | | |
|
||||
| WindowDidResize | | | | |
|
||||
| WindowDidHide | | | | |
|
||||
| ApplicationWillTerminate | | | | |
|
||||
|
||||
... Add more
|
||||
|
||||
## Bindings Generation
|
||||
|
||||
TBD
|
||||
|
||||
## Models Generation
|
||||
|
||||
TBD
|
||||
|
||||
## Task file
|
||||
|
||||
TBD
|
||||
|
||||
## Theme
|
||||
|
||||
| Mode | Windows | Linux | Mac | Notes |
|
||||
|--------|---------|-------|-----|-------|
|
||||
| Dark | Y | | | |
|
||||
| Light | Y | | | |
|
||||
| System | Y | | | |
|
||||
|
||||
## NSIS Installer
|
||||
|
||||
TBD
|
||||
|
||||
## Templates
|
||||
|
||||
TBD
|
||||
|
||||
## Plugins
|
||||
|
||||
Built-in plugin support:
|
||||
|
||||
| Plugin | Windows | Linux | Mac | Notes |
|
||||
|-----------------|---------|-------|-----|-------|
|
||||
| Browser | | | Y | |
|
||||
| KV Store | | | Y | |
|
||||
| Log | | | Y | |
|
||||
| Single Instance | | | Y | |
|
||||
| SQLite | | | Y | |
|
||||
| Start at login | | | Y | |
|
||||
| Server | | | | |
|
||||
|
||||
## Packaging
|
||||
|
||||
| | Windows | Linux | Mac | Notes |
|
||||
|-----------------|---------|-------|-----|-------|
|
||||
| Icon Generation | | | Y | |
|
||||
| Icon Embedding | | | Y | |
|
||||
| Info.plist | | | Y | |
|
||||
| NSIS Installer | | | - | |
|
||||
| Mac bundle | | | Y | |
|
||||
| Windows exe | | | - | |
|
||||
|
||||
## Frameless Windows
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------|---------|-------|-----|-------|
|
||||
| Resize | | | | |
|
||||
| Drag | | | | |
|
||||
|
||||
## Mac Specific
|
||||
|
||||
- [x] Translucency
|
||||
|
||||
## Windows Specific
|
||||
|
||||
- [x] Translucency
|
||||
- [x] Custom Themes
|
||||
|
||||
### Windows Options
|
||||
|
||||
| Feature | Default | Notes |
|
||||
|-----------------------------------|---------|---------------------------------------------|
|
||||
| BackdropType | | |
|
||||
| DisableIcon | | |
|
||||
| Theme | | |
|
||||
| CustomTheme | | |
|
||||
| DisableFramelessWindowDecorations | | |
|
||||
| WindowMask | nil | Makes the window the contents of the bitmap |
|
||||
|
||||
// Select the type of translucent backdrop. Requires Windows 11 22621 or later.
|
||||
BackdropType BackdropType
|
||||
// Disable the icon in the titlebar
|
||||
DisableIcon bool
|
||||
// Theme. Defaults to SystemDefault which will use whatever the system theme is. The application will follow system theme changes.
|
||||
Theme Theme
|
||||
// Custom colours for dark/light mode
|
||||
CustomTheme *ThemeSettings
|
||||
|
||||
// Disable all window decorations in Frameless mode, which means no "Aero Shadow" and no "Rounded Corner" will be shown.
|
||||
// "Rounded Corners" are only available on Windows 11.
|
||||
DisableFramelessWindowDecorations bool
|
||||
|
||||
// WindowMask is used to set the window shape. Use a PNG with an alpha channel to create a custom shape.
|
||||
WindowMask []byte
|
||||
|
||||
## Linux Specific
|
||||
49
v3/TODO.md
49
v3/TODO.md
|
|
@ -1,49 +0,0 @@
|
|||
# TODO
|
||||
|
||||
Informal and incomplete list of things needed in v3.
|
||||
|
||||
## General
|
||||
|
||||
- [x] Generate Bindings
|
||||
- [x] Generate TS Models
|
||||
- [ ] Dev Mode
|
||||
- [ ] Generate Info.Plist from `info.json`
|
||||
|
||||
- [ ] Windows Port
|
||||
- [ ] Linux Port
|
||||
|
||||
## Runtime
|
||||
|
||||
- [x] Pass window ID with window calls in JS
|
||||
- [x] Implement alias for `window` in JS
|
||||
- [x] Implement runtime dispatcher
|
||||
- [x] Log
|
||||
- [x] Same Window
|
||||
- [ ] Other Window
|
||||
- [x] Dialogs
|
||||
- [x] Info
|
||||
- [x] Warning
|
||||
- [x] Error
|
||||
- [x] Question
|
||||
- [x] OpenFile
|
||||
- [x] SaveFile
|
||||
- [x] Events
|
||||
- [x] Screens
|
||||
- [x] Clipboard
|
||||
- [x] Application
|
||||
- [ ] Create `.d.ts` file
|
||||
|
||||
## Templates
|
||||
|
||||
- [ ] Create plain template
|
||||
- [ ] Improve default template
|
||||
|
||||
## Runtime
|
||||
|
||||
- [ ] To log or not to log?
|
||||
- [ ] Unify cross-platform events, eg. `onClose`
|
||||
|
||||
## Plugins
|
||||
|
||||
- [ ] Move logins to `v3/plugins`
|
||||
- [ ] Expose application logger to plugins
|
||||
|
|
@ -180,3 +180,39 @@ const MyEnum = {
|
|||
|
||||
- Why use `float64`? Can't we use `int`?
|
||||
- Because JavaScript doesn't have a concept of `int`. Everything is a `number`, which translates to `float64` in Go. There are also restrictions on casting types in Go's reflection package, which means using `int` doesn't work.
|
||||
|
||||
### BackgroundColour
|
||||
|
||||
In v2, this was a pointer to an `RGBA` struct. In v3, this is an `RGBA` struct value.
|
||||
|
||||
### WindowIsTranslucent
|
||||
|
||||
This flag has been removed. Now there is a `BackgroundType` flag that can be used to set the type of background the window should have.
|
||||
This flag can be set to any of the following values:
|
||||
- `BackgroundTypeSolid` - The window will have a solid background
|
||||
- `BackgroundTypeTransparent` - The window will have a transparent background
|
||||
- `BackgroundTypeTranslucent` - The window will have a translucent background
|
||||
|
||||
On Windows, if the `BackgroundType` is set to `BackgroundTypeTranslucent`, the type of translucency can be set using the
|
||||
`BackdropType` flag in the `WindowsWindow` options. This can be set to any of the following values:
|
||||
- `Auto` - The window will use an effect determined by the system
|
||||
- `None` - The window will have no background
|
||||
- `Mica` - The window will use the Mica effect
|
||||
- `Acrylic` - The window will use the acrylic effect
|
||||
- `Tabbed` - The window will use the tabbed effect
|
||||
|
||||
|
||||
## Windows Application Options
|
||||
|
||||
### WndProcInterceptor
|
||||
|
||||
If this is set, the WndProc will be intercepted and the function will be called. This allows you to handle Windows
|
||||
messages directly. The function should have the following signature:
|
||||
|
||||
```go
|
||||
func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnValue uintptr, shouldReturn)
|
||||
```
|
||||
|
||||
The `shouldReturn` value should be set to `true` if the returnValue should be returned by the main wndProc method.
|
||||
If it is set to `false`, the return value will be ignored and the message will continue to be processed by the main
|
||||
wndProc method.
|
||||
|
|
@ -14,6 +14,7 @@ require (
|
|||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/wailsapp/wails/v3 => ../..
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
|||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
|
|||
|
|
@ -19,14 +19,15 @@ func main() {
|
|||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
Assets: application.AssetOptions{
|
||||
FS: assets,
|
||||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
URL: "/",
|
||||
})
|
||||
|
||||
err := app.Run()
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func main() {
|
|||
if runtime.GOOS == "darwin" {
|
||||
myMenu.Add("New WebviewWindow (MacTitleBarHiddenInset)").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Mac: application.MacWindow{
|
||||
TitleBar: application.MacTitleBarHiddenInset,
|
||||
InvisibleTitleBarHeight: 25,
|
||||
|
|
@ -81,7 +81,7 @@ func main() {
|
|||
})
|
||||
myMenu.Add("New WebviewWindow (MacTitleBarHiddenInsetUnified)").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Mac: application.MacWindow{
|
||||
TitleBar: application.MacTitleBarHiddenInsetUnified,
|
||||
InvisibleTitleBarHeight: 50,
|
||||
|
|
@ -95,7 +95,7 @@ func main() {
|
|||
})
|
||||
myMenu.Add("New WebviewWindow (MacTitleBarHidden)").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Mac: application.MacWindow{
|
||||
TitleBar: application.MacTitleBarHidden,
|
||||
InvisibleTitleBarHeight: 25,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
|
||||
mainWindow := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Context Menu Demo",
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
|
|
@ -34,7 +34,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Context Menu Demo",
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/wailsapp/wails/v3/pkg/icons"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
|
|
@ -45,7 +46,7 @@ func main() {
|
|||
dialog := app.InfoDialog()
|
||||
dialog.SetTitle("Custom Icon Example")
|
||||
dialog.SetMessage("Using a custom icon")
|
||||
dialog.SetIcon(application.DefaultApplicationIcon)
|
||||
dialog.SetIcon(icons.ApplicationDarkMode256)
|
||||
dialog.Show()
|
||||
})
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ func main() {
|
|||
dialog := app.QuestionDialog()
|
||||
dialog.SetTitle("Custom Icon Example")
|
||||
dialog.SetMessage("Using a custom icon")
|
||||
dialog.SetIcon(application.WailsLogoWhiteTransparent)
|
||||
dialog.SetIcon(icons.WailsLogoWhiteTransparent)
|
||||
dialog.SetDefaultButton(dialog.AddButton("I like it!"))
|
||||
dialog.AddButton("Not so keen...")
|
||||
dialog.Show()
|
||||
|
|
@ -112,7 +113,7 @@ func main() {
|
|||
dialog := app.WarningDialog()
|
||||
dialog.SetTitle("Custom Icon Example")
|
||||
dialog.SetMessage("Using a custom icon")
|
||||
dialog.SetIcon(application.DefaultApplicationIcon)
|
||||
dialog.SetIcon(icons.ApplicationLightMode256)
|
||||
dialog.Show()
|
||||
})
|
||||
|
||||
|
|
@ -137,7 +138,7 @@ func main() {
|
|||
dialog := app.ErrorDialog()
|
||||
dialog.SetTitle("Custom Icon Example")
|
||||
dialog.SetMessage("Using a custom icon")
|
||||
dialog.SetIcon(application.WailsLogoWhite)
|
||||
dialog.SetIcon(icons.WailsLogoWhite)
|
||||
dialog.Show()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
|
||||
window := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Drag-n-drop Demo",
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func main() {
|
|||
}
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Events Demo",
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
|
|
@ -49,7 +49,7 @@ func main() {
|
|||
InvisibleTitleBarHeight: 50,
|
||||
},
|
||||
})
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Events Demo",
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/wailsapp/wails/v3/pkg/icons"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
|
@ -62,9 +63,9 @@ func main() {
|
|||
mySystray := app.NewSystemTray()
|
||||
mySystray.SetLabel("Wails")
|
||||
if runtime.GOOS == "darwin" {
|
||||
mySystray.SetTemplateIcon(application.DefaultMacTemplateIcon)
|
||||
mySystray.SetTemplateIcon(icons.SystrayMacTemplate)
|
||||
} else {
|
||||
mySystray.SetIcon(application.DefaultApplicationIcon)
|
||||
mySystray.SetIcon(icons.ApplicationDarkMode256)
|
||||
}
|
||||
myMenu := app.NewMenu()
|
||||
myMenu.Add("Item 1")
|
||||
|
|
@ -102,20 +103,20 @@ func main() {
|
|||
mySystray := app.NewSystemTray()
|
||||
mySystray.SetLabel("Wails is awesome")
|
||||
if runtime.GOOS == "darwin" {
|
||||
mySystray.SetTemplateIcon(application.DefaultMacTemplateIcon)
|
||||
mySystray.SetTemplateIcon(icons.SystrayMacTemplate)
|
||||
} else {
|
||||
mySystray.SetIcon(application.DefaultApplicationIcon)
|
||||
mySystray.SetIcon(icons.ApplicationDarkMode256)
|
||||
}
|
||||
mySystray.SetMenu(myMenu)
|
||||
mySystray.SetIconPosition(application.NSImageLeading)
|
||||
|
||||
myWindow := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
myWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Kitchen Sink",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
AlwaysOnTop: true,
|
||||
DisableResize: false,
|
||||
BackgroundColour: &application.RGBA{
|
||||
BackgroundColour: application.RGBA{
|
||||
Red: 255,
|
||||
Green: 255,
|
||||
Blue: 255,
|
||||
|
|
@ -184,7 +185,7 @@ func main() {
|
|||
*/
|
||||
var myWindow2 *application.WebviewWindow
|
||||
var myWindow2Lock sync.RWMutex
|
||||
myWindow2 = app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
myWindow2 = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "#2",
|
||||
Width: 1024,
|
||||
Height: 768,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
_ "embed"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
|
@ -23,7 +24,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; user-select: none; -ms-user-select: none; -webkit-user-select: none; } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
@ -38,6 +39,21 @@ func main() {
|
|||
println("clicked")
|
||||
})
|
||||
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle new Window from GoRoutine",
|
||||
Width: 500,
|
||||
Height: 500,
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
TitleBar: application.MacTitleBarHiddenInsetUnified,
|
||||
InvisibleTitleBarHeight: 50,
|
||||
},
|
||||
})
|
||||
}()
|
||||
|
||||
err := app.Run()
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Screen Demo",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,21 @@ package main
|
|||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v3/pkg/icons"
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
var counter int
|
||||
|
||||
func clickCount() int {
|
||||
counter++
|
||||
return counter
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Systray Demo",
|
||||
|
|
@ -17,18 +26,22 @@ func main() {
|
|||
},
|
||||
})
|
||||
|
||||
window := app.NewWebviewWindow().Hide()
|
||||
|
||||
systemTray := app.NewSystemTray()
|
||||
if runtime.GOOS == "darwin" {
|
||||
systemTray.SetIcon(application.DefaultMacTemplateIcon)
|
||||
systemTray.SetTemplateIcon(icons.SystrayMacTemplate)
|
||||
}
|
||||
|
||||
myMenu := app.NewMenu()
|
||||
myMenu.Add("Hello World!").OnClick(func(ctx *application.Context) {
|
||||
app.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show()
|
||||
println("Hello World!")
|
||||
// app.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show()
|
||||
})
|
||||
subMenu := myMenu.AddSubmenu("Submenu")
|
||||
subMenu.Add("Click me!").OnClick(func(ctx *application.Context) {
|
||||
ctx.ClickedMenuItem().SetLabel("Clicked!")
|
||||
println("Click me!")
|
||||
// ctx.ClickedMenuItem().SetLabel("Clicked!")
|
||||
})
|
||||
myMenu.AddSeparator()
|
||||
myMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
|
|
@ -37,6 +50,11 @@ func main() {
|
|||
|
||||
systemTray.SetMenu(myMenu)
|
||||
|
||||
systemTray.OnClick(func() {
|
||||
window.SetTitle(fmt.Sprintf("Clicked %d times", clickCount()))
|
||||
window.Show().Focus()
|
||||
})
|
||||
|
||||
err := app.Run()
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ func main() {
|
|||
myMenu.Add("New WebviewWindow (Hide on Close").
|
||||
SetAccelerator("CmdOrCtrl+H").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{HideOnClose: true}).
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{HideOnClose: true}).
|
||||
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
|
||||
SetPosition(rand.Intn(1000), rand.Intn(800)).
|
||||
SetURL("https://wails.io").
|
||||
|
|
@ -66,7 +66,7 @@ func main() {
|
|||
myMenu.Add("New Frameless WebviewWindow").
|
||||
SetAccelerator("CmdOrCtrl+F").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
X: rand.Intn(1000),
|
||||
Y: rand.Intn(800),
|
||||
Frameless: true,
|
||||
|
|
@ -79,7 +79,7 @@ func main() {
|
|||
if runtime.GOOS == "darwin" {
|
||||
myMenu.Add("New WebviewWindow (MacTitleBarHiddenInset)").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Mac: application.MacWindow{
|
||||
TitleBar: application.MacTitleBarHiddenInset,
|
||||
InvisibleTitleBarHeight: 25,
|
||||
|
|
@ -93,7 +93,7 @@ func main() {
|
|||
})
|
||||
myMenu.Add("New WebviewWindow (MacTitleBarHiddenInsetUnified)").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Mac: application.MacWindow{
|
||||
TitleBar: application.MacTitleBarHiddenInsetUnified,
|
||||
InvisibleTitleBarHeight: 50,
|
||||
|
|
@ -107,7 +107,7 @@ func main() {
|
|||
})
|
||||
myMenu.Add("New WebviewWindow (MacTitleBarHidden)").
|
||||
OnClick(func(ctx *application.Context) {
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Mac: application.MacWindow{
|
||||
TitleBar: application.MacTitleBarHidden,
|
||||
InvisibleTitleBarHeight: 25,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func main() {
|
|||
|
||||
newWindow := func() {
|
||||
windowName := "WebviewWindow " + strconv.Itoa(windowCounter)
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Name: windowName,
|
||||
}).
|
||||
SetTitle(windowName).
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Wails ML Demo",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ require (
|
|||
github.com/samber/lo v1.37.0
|
||||
github.com/tc-hib/winres v0.1.6
|
||||
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6
|
||||
golang.org/x/sys v0.7.0
|
||||
modernc.org/sqlite v1.21.0
|
||||
)
|
||||
|
||||
|
|
@ -53,7 +54,6 @@ require (
|
|||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
|
|
|
|||
|
|
@ -182,8 +182,8 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -124,3 +126,39 @@ func generateWindowsIcon(iconData []byte, sizes []int, options *IconsOptions) er
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateTemplateIcon(data []byte, outputFilename string) error {
|
||||
// Decode the input file as a PNG
|
||||
buffer := bytes.NewBuffer(data)
|
||||
img, err := png.Decode(buffer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode input file as PNG: %w", err)
|
||||
}
|
||||
|
||||
// Create a new image with the same dimensions and RGBA color model
|
||||
bounds := img.Bounds()
|
||||
iconImg := image.NewRGBA(bounds)
|
||||
|
||||
// Iterate over each pixel of the input image
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
// Get the alpha of the pixel
|
||||
_, _, _, a := img.At(x, y).RGBA()
|
||||
iconImg.SetRGBA(x, y, color.RGBA{R: 0, G: 0, B: 0, A: uint8(a)})
|
||||
}
|
||||
}
|
||||
|
||||
// Create the output file
|
||||
outFile, err := os.Create(outputFilename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
// Encode the template icon image as a PNG and write it to the output file
|
||||
if err := png.Encode(outFile, iconImg); err != nil {
|
||||
return fmt.Errorf("failed to encode output image as PNG: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package debug
|
||||
|
||||
import (
|
||||
"github.com/samber/lo"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
"runtime/debug"
|
||||
|
||||
import "runtime/debug"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Why go doesn't provide this as a map already is beyond me.
|
||||
var buildSettings = map[string]string{}
|
||||
|
|
@ -20,7 +20,7 @@ func init() {
|
|||
buildSettings = lo.Associate(buildInfo.Settings, func(setting debug.BuildSetting) (string, string) {
|
||||
return setting.Key, setting.Value
|
||||
})
|
||||
if isLocalBuild() {
|
||||
if isLocalBuild() || buildInfo.Path == "" {
|
||||
modulePath := RelativePath("..", "..", "..")
|
||||
LocalModulePath, _ = filepath.Abs(modulePath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ require (
|
|||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ package templates
|
|||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/wailsapp/wails/v3/internal/debug"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/flags"
|
||||
|
||||
|
|
@ -154,7 +156,7 @@ func Install(options *flags.Init) error {
|
|||
|
||||
templateData := TemplateOptions{
|
||||
options,
|
||||
debug.LocalModulePath,
|
||||
filepath.FromSlash(debug.LocalModulePath + "/"),
|
||||
}
|
||||
template, found := lo.Find(defaultTemplates, func(template TemplateData) bool {
|
||||
return template.Name == options.TemplateName
|
||||
|
|
@ -167,7 +169,7 @@ func Install(options *flags.Init) error {
|
|||
templateData.ProjectDir = lo.Must(os.Getwd())
|
||||
}
|
||||
templateData.ProjectDir = fmt.Sprintf("%s/%s", options.ProjectDir, options.ProjectName)
|
||||
fmt.Printf("Installing template '%s' into '%s'\n", options.TemplateName, options.ProjectDir)
|
||||
pterm.Printf("Installing template '%s' into '%s'\n", options.TemplateName, filepath.FromSlash(options.ProjectDir))
|
||||
tfs, err := fs.Sub(template.FS, options.TemplateName)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ require (
|
|||
golang.org/x/net v0.7.0 // indirect
|
||||
)
|
||||
{{if gt (len .LocalModulePath) 0}}
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}/v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}/v2
|
||||
replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3
|
||||
replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
},
|
||||
})
|
||||
// Create window
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Plain Bundle",
|
||||
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
|
||||
Mac: application.MacWindow{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package application
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/icons"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
|
@ -9,6 +9,8 @@ import (
|
|||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
assetserveroptions "github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
|
|
@ -24,6 +26,10 @@ func init() {
|
|||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
type EventListener struct {
|
||||
callback func()
|
||||
}
|
||||
|
||||
func New(appOptions Options) *App {
|
||||
if globalApplication != nil {
|
||||
return globalApplication
|
||||
|
|
@ -33,7 +39,7 @@ func New(appOptions Options) *App {
|
|||
|
||||
result := &App{
|
||||
options: appOptions,
|
||||
applicationEventListeners: make(map[uint][]func()),
|
||||
applicationEventListeners: make(map[uint][]*EventListener),
|
||||
systemTrays: make(map[uint]*SystemTray),
|
||||
log: logger.New(appOptions.Logger.CustomLoggers...),
|
||||
contextMenus: make(map[string]*Menu),
|
||||
|
|
@ -92,24 +98,28 @@ func mergeApplicationDefaults(o *Options) {
|
|||
o.Description = "An application written using Wails"
|
||||
}
|
||||
if o.Icon == nil {
|
||||
o.Icon = DefaultApplicationIcon
|
||||
o.Icon = icons.ApplicationLightMode256
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type platformApp interface {
|
||||
run() error
|
||||
destroy()
|
||||
setApplicationMenu(menu *Menu)
|
||||
name() string
|
||||
getCurrentWindowID() uint
|
||||
showAboutDialog(name string, description string, icon []byte)
|
||||
setIcon(icon []byte)
|
||||
on(id uint)
|
||||
dispatchOnMainThread(id uint)
|
||||
hide()
|
||||
show()
|
||||
}
|
||||
type (
|
||||
platformApp interface {
|
||||
run() error
|
||||
destroy()
|
||||
setApplicationMenu(menu *Menu)
|
||||
name() string
|
||||
getCurrentWindowID() uint
|
||||
showAboutDialog(name string, description string, icon []byte)
|
||||
setIcon(icon []byte)
|
||||
on(id uint)
|
||||
dispatchOnMainThread(id uint)
|
||||
hide()
|
||||
show()
|
||||
getPrimaryScreen() (*Screen, error)
|
||||
getScreens() ([]*Screen, error)
|
||||
}
|
||||
)
|
||||
|
||||
// Messages sent from javascript get routed here
|
||||
type windowMessage struct {
|
||||
|
|
@ -152,7 +162,7 @@ var webviewRequests = make(chan *webViewAssetRequest)
|
|||
|
||||
type App struct {
|
||||
options Options
|
||||
applicationEventListeners map[uint][]func()
|
||||
applicationEventListeners map[uint][]*EventListener
|
||||
applicationEventListenersLock sync.RWMutex
|
||||
|
||||
// Windows
|
||||
|
|
@ -213,17 +223,28 @@ func (a *App) deleteWindowByID(id uint) {
|
|||
delete(a.windows, id)
|
||||
}
|
||||
|
||||
func (a *App) On(eventType events.ApplicationEventType, callback func()) {
|
||||
func (a *App) On(eventType events.ApplicationEventType, callback func()) func() {
|
||||
eventID := uint(eventType)
|
||||
a.applicationEventListenersLock.Lock()
|
||||
defer a.applicationEventListenersLock.Unlock()
|
||||
a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], callback)
|
||||
listener := &EventListener{
|
||||
callback: callback,
|
||||
}
|
||||
a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], listener)
|
||||
if a.impl != nil {
|
||||
go a.impl.on(eventID)
|
||||
}
|
||||
|
||||
return func() {
|
||||
// lock the map
|
||||
a.applicationEventListenersLock.Lock()
|
||||
defer a.applicationEventListenersLock.Unlock()
|
||||
// Remove listener
|
||||
a.applicationEventListeners[eventID] = lo.Without(a.applicationEventListeners[eventID], listener)
|
||||
}
|
||||
}
|
||||
func (a *App) NewWebviewWindow() *WebviewWindow {
|
||||
return a.NewWebviewWindowWithOptions(&WebviewWindowOptions{})
|
||||
return a.NewWebviewWindowWithOptions(WebviewWindowOptions{})
|
||||
}
|
||||
|
||||
func (a *App) GetPID() int {
|
||||
|
|
@ -264,11 +285,7 @@ func (a *App) error(message string, args ...any) {
|
|||
})
|
||||
}
|
||||
|
||||
func (a *App) NewWebviewWindowWithOptions(windowOptions *WebviewWindowOptions) *WebviewWindow {
|
||||
// Ensure we have sane defaults
|
||||
if windowOptions == nil {
|
||||
windowOptions = WebviewWindowDefaults
|
||||
}
|
||||
func (a *App) NewWebviewWindowWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow {
|
||||
newWindow := NewWindow(windowOptions)
|
||||
id := newWindow.id
|
||||
if a.windows == nil {
|
||||
|
|
@ -324,10 +341,6 @@ func (a *App) Run() error {
|
|||
for {
|
||||
request := <-webviewRequests
|
||||
a.handleWebViewRequest(request)
|
||||
err := request.Release()
|
||||
if err != nil {
|
||||
a.error("Failed to release webview request: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
|
|
@ -361,10 +374,10 @@ func (a *App) Run() error {
|
|||
}
|
||||
|
||||
// set the application menu
|
||||
a.impl.setApplicationMenu(a.ApplicationMenu)
|
||||
|
||||
// set the application Icon
|
||||
a.impl.setIcon(a.options.Icon)
|
||||
if runtime.GOOS == "darwin" {
|
||||
a.impl.setApplicationMenu(a.ApplicationMenu)
|
||||
a.impl.setIcon(a.options.Icon)
|
||||
}
|
||||
|
||||
err := a.impl.run()
|
||||
if err != nil {
|
||||
|
|
@ -384,7 +397,7 @@ func (a *App) handleApplicationEvent(event uint) {
|
|||
return
|
||||
}
|
||||
for _, listener := range listeners {
|
||||
go listener()
|
||||
go listener.callback()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -518,11 +531,11 @@ func (a *App) SaveFileDialog() *SaveFileDialog {
|
|||
}
|
||||
|
||||
func (a *App) GetPrimaryScreen() (*Screen, error) {
|
||||
return getPrimaryScreen()
|
||||
return a.impl.getPrimaryScreen()
|
||||
}
|
||||
|
||||
func (a *App) GetScreens() ([]*Screen, error) {
|
||||
return getScreens()
|
||||
return a.impl.getScreens()
|
||||
}
|
||||
|
||||
func (a *App) Clipboard() *Clipboard {
|
||||
|
|
@ -603,3 +616,35 @@ func (a *App) GetWindowByName(name string) *WebviewWindow {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func invokeSync(fn func()) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
fn()
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func invokeSyncWithResult[T any](fn func() T) (res T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
res = fn()
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return res
|
||||
}
|
||||
|
||||
func invokeSyncWithResultAndError[T any](fn func() (T, error)) (res T, err error) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
res, err = fn()
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return res, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,10 @@ type macosApp struct {
|
|||
parent *App
|
||||
}
|
||||
|
||||
func getNativeApplication() *macosApp {
|
||||
return globalApplication.impl.(*macosApp)
|
||||
}
|
||||
|
||||
func (m *macosApp) hide() {
|
||||
C.hide()
|
||||
}
|
||||
|
|
|
|||
246
v3/pkg/application/application_windows.go
Normal file
246
v3/pkg/application/application_windows.go
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
//go:build windows
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var windowClassName = lo.Must(syscall.UTF16PtrFromString("WailsWebviewWindow"))
|
||||
|
||||
type windowsApp struct {
|
||||
parent *App
|
||||
|
||||
instance w32.HINSTANCE
|
||||
|
||||
windowMap map[w32.HWND]*windowsWebviewWindow
|
||||
systrayMap map[w32.HMENU]*windowsSystemTray
|
||||
|
||||
mainThreadID w32.HANDLE
|
||||
mainThreadWindowHWND w32.HWND
|
||||
|
||||
// Windows hidden by application.Hide()
|
||||
hiddenWindows []*windowsWebviewWindow
|
||||
focusedWindow w32.HWND
|
||||
|
||||
// system theme
|
||||
isDarkMode bool
|
||||
}
|
||||
|
||||
func getNativeApplication() *windowsApp {
|
||||
return globalApplication.impl.(*windowsApp)
|
||||
}
|
||||
|
||||
func (m *windowsApp) getPrimaryScreen() (*Screen, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m *windowsApp) getScreens() ([]*Screen, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m *windowsApp) hide() {
|
||||
// Get the current focussed window
|
||||
m.focusedWindow = w32.GetForegroundWindow()
|
||||
|
||||
// Iterate over all windows and hide them if they aren't already hidden
|
||||
for _, window := range m.windowMap {
|
||||
if window.isVisible() {
|
||||
// Add to hidden windows
|
||||
m.hiddenWindows = append(m.hiddenWindows, window)
|
||||
window.hide()
|
||||
}
|
||||
}
|
||||
// Switch focus to the next application
|
||||
hwndNext := w32.GetWindow(m.mainThreadWindowHWND, w32.GW_HWNDNEXT)
|
||||
w32.SetForegroundWindow(hwndNext)
|
||||
}
|
||||
|
||||
func (m *windowsApp) show() {
|
||||
// Iterate over all windows and show them if they were previously hidden
|
||||
for _, window := range m.hiddenWindows {
|
||||
window.show()
|
||||
}
|
||||
// Show the foreground window
|
||||
w32.SetForegroundWindow(m.focusedWindow)
|
||||
}
|
||||
|
||||
func (m *windowsApp) on(eventID uint) {
|
||||
//C.registerListener(C.uint(eventID))
|
||||
}
|
||||
|
||||
func (m *windowsApp) setIcon(icon []byte) {
|
||||
//C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon)))
|
||||
}
|
||||
|
||||
func (m *windowsApp) name() string {
|
||||
//appName := C.getAppName()
|
||||
//defer C.free(unsafe.Pointer(appName))
|
||||
//return C.GoString(appName)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *windowsApp) getCurrentWindowID() uint {
|
||||
//return uint(C.getCurrentWindowID())
|
||||
return uint(0)
|
||||
}
|
||||
|
||||
func (m *windowsApp) setApplicationMenu(menu *Menu) {
|
||||
if menu == nil {
|
||||
// Create a default menu for windows
|
||||
menu = defaultApplicationMenu()
|
||||
}
|
||||
menu.Update()
|
||||
|
||||
// Convert impl to macosMenu object
|
||||
//m.applicationMenu = (menu.impl).(*macosMenu).nsMenu
|
||||
//C.setApplicationMenu(m.applicationMenu)
|
||||
}
|
||||
|
||||
func (m *windowsApp) run() error {
|
||||
// Add a hook to the ApplicationDidFinishLaunching event
|
||||
//m.parent.On(events.Mac.ApplicationDidFinishLaunching, func() {
|
||||
// C.setApplicationShouldTerminateAfterLastWindowClosed(C.bool(m.parent.options.Mac.ApplicationShouldTerminateAfterLastWindowClosed))
|
||||
// C.setActivationPolicy(C.int(m.parent.options.Mac.ActivationPolicy))
|
||||
// C.activateIgnoringOtherApps()
|
||||
//})
|
||||
// setup event listeners
|
||||
for eventID := range m.parent.applicationEventListeners {
|
||||
m.on(eventID)
|
||||
}
|
||||
|
||||
_ = m.runMainLoop()
|
||||
|
||||
//C.run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *windowsApp) destroy() {
|
||||
//C.destroyApp()
|
||||
}
|
||||
|
||||
func (m *windowsApp) init() {
|
||||
// Register the window class
|
||||
|
||||
icon := w32.LoadIconWithResourceID(m.instance, w32.IDI_APPLICATION)
|
||||
|
||||
var wc w32.WNDCLASSEX
|
||||
wc.Size = uint32(unsafe.Sizeof(wc))
|
||||
wc.Style = w32.CS_HREDRAW | w32.CS_VREDRAW
|
||||
wc.WndProc = syscall.NewCallback(m.wndProc)
|
||||
wc.Instance = m.instance
|
||||
wc.Background = w32.COLOR_BTNFACE + 1
|
||||
wc.Icon = icon
|
||||
wc.Cursor = w32.LoadCursorWithResourceID(0, w32.IDC_ARROW)
|
||||
wc.ClassName = windowClassName
|
||||
wc.MenuName = nil
|
||||
wc.IconSm = icon
|
||||
|
||||
if ret := w32.RegisterClassEx(&wc); ret == 0 {
|
||||
panic(syscall.GetLastError())
|
||||
}
|
||||
|
||||
m.isDarkMode = w32.IsCurrentlyDarkMode()
|
||||
}
|
||||
|
||||
func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
|
||||
// Handle the invoke callback
|
||||
if msg == wmInvokeCallback {
|
||||
m.invokeCallback(wParam, lParam)
|
||||
return 0
|
||||
}
|
||||
|
||||
// If the WndProcInterceptor is set in options, pass the message on
|
||||
if m.parent.options.Windows.WndProcInterceptor != nil {
|
||||
returnValue, shouldReturn := m.parent.options.Windows.WndProcInterceptor(hwnd, msg, wParam, lParam)
|
||||
if shouldReturn {
|
||||
return returnValue
|
||||
}
|
||||
}
|
||||
|
||||
switch msg {
|
||||
case w32.WM_SETTINGCHANGE:
|
||||
settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam)))
|
||||
if settingChanged == "ImmersiveColorSet" {
|
||||
isDarkMode := w32.IsCurrentlyDarkMode()
|
||||
if isDarkMode != m.isDarkMode {
|
||||
applicationEvents <- uint(events.Windows.SystemThemeChanged)
|
||||
m.isDarkMode = isDarkMode
|
||||
}
|
||||
}
|
||||
return 0
|
||||
case w32.WM_POWERBROADCAST:
|
||||
switch wParam {
|
||||
case w32.PBT_APMPOWERSTATUSCHANGE:
|
||||
applicationEvents <- uint(events.Windows.APMPowerStatusChange)
|
||||
case w32.PBT_APMSUSPEND:
|
||||
applicationEvents <- uint(events.Windows.APMSuspend)
|
||||
case w32.PBT_APMRESUMEAUTOMATIC:
|
||||
applicationEvents <- uint(events.Windows.APMResumeAutomatic)
|
||||
case w32.PBT_APMRESUMESUSPEND:
|
||||
applicationEvents <- uint(events.Windows.APMResumeSuspend)
|
||||
case w32.PBT_POWERSETTINGCHANGE:
|
||||
applicationEvents <- uint(events.Windows.APMPowerSettingChange)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if window, ok := m.windowMap[hwnd]; ok {
|
||||
return window.WndProc(msg, wParam, lParam)
|
||||
}
|
||||
|
||||
if systray, ok := m.systrayMap[hwnd]; ok {
|
||||
return systray.wndProc(msg, wParam, lParam)
|
||||
}
|
||||
|
||||
// Dispatch the message to the appropriate window
|
||||
|
||||
return w32.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
func (m *windowsApp) registerWindow(result *windowsWebviewWindow) {
|
||||
m.windowMap[result.hwnd] = result
|
||||
}
|
||||
|
||||
func (m *windowsApp) registerSystemTray(result *windowsSystemTray) {
|
||||
m.systrayMap[result.hwnd] = result
|
||||
}
|
||||
|
||||
func (m *windowsApp) unregisterWindow(w *windowsWebviewWindow) {
|
||||
delete(m.windowMap, w.hwnd)
|
||||
|
||||
// If this was the last window...
|
||||
if len(m.windowMap) == 0 {
|
||||
w32.PostQuitMessage(0)
|
||||
}
|
||||
}
|
||||
|
||||
func newPlatformApp(app *App) *windowsApp {
|
||||
err := w32.SetProcessDPIAware()
|
||||
if err != nil {
|
||||
println("Fatal error in application initialisation: ", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
result := &windowsApp{
|
||||
parent: app,
|
||||
instance: w32.GetModuleHandle(""),
|
||||
windowMap: make(map[w32.HWND]*windowsWebviewWindow),
|
||||
systrayMap: make(map[w32.HWND]*windowsSystemTray),
|
||||
}
|
||||
|
||||
result.init()
|
||||
result.initMainLoop()
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
package application
|
||||
|
||||
import "C"
|
||||
|
||||
type clipboardImpl interface {
|
||||
setText(text string) bool
|
||||
text() string
|
||||
|
|
|
|||
28
v3/pkg/application/clipboard_windows.go
Normal file
28
v3/pkg/application/clipboard_windows.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
//go:build windows
|
||||
|
||||
package application
|
||||
|
||||
type windowsClipboard struct{}
|
||||
|
||||
func (m windowsClipboard) setText(text string) bool {
|
||||
//clipboardLock.Lock()
|
||||
//defer clipboardLock.Unlock()
|
||||
//cText := C.CString(text)
|
||||
//success := C.setClipboardText(cText)
|
||||
//C.free(unsafe.Pointer(cText))
|
||||
//return bool(success)
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m windowsClipboard) text() string {
|
||||
//clipboardLock.RLock()
|
||||
//defer clipboardLock.RUnlock()
|
||||
//clipboardText := C.getClipboardText()
|
||||
//result := C.GoString(clipboardText)
|
||||
//return result
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newClipboardImpl() *windowsClipboard {
|
||||
return &windowsClipboard{}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package application
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -86,7 +85,6 @@ func newMessageDialog(dialogType DialogType) *MessageDialog {
|
|||
return &MessageDialog{
|
||||
MessageDialogOptions: MessageDialogOptions{
|
||||
DialogType: dialogType,
|
||||
Title: defaultTitles[dialogType],
|
||||
},
|
||||
impl: nil,
|
||||
}
|
||||
|
|
@ -101,7 +99,7 @@ func (d *MessageDialog) Show() {
|
|||
if d.impl == nil {
|
||||
d.impl = newDialogImpl(d)
|
||||
}
|
||||
d.impl.show()
|
||||
invokeSync(d.impl.show)
|
||||
}
|
||||
|
||||
func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog {
|
||||
|
|
@ -249,7 +247,7 @@ func (d *OpenFileDialog) PromptForSingleSelection() (string, error) {
|
|||
if d.impl == nil {
|
||||
d.impl = newOpenFileDialogImpl(d)
|
||||
}
|
||||
selection, err := d.impl.show()
|
||||
selection, err := invokeSyncWithResultAndError(d.impl.show)
|
||||
var result string
|
||||
if len(selection) > 0 {
|
||||
result = selection[0]
|
||||
|
|
@ -273,7 +271,7 @@ func (d *OpenFileDialog) PromptForMultipleSelection() ([]string, error) {
|
|||
if d.impl == nil {
|
||||
d.impl = newOpenFileDialogImpl(d)
|
||||
}
|
||||
return d.impl.show()
|
||||
return invokeSyncWithResultAndError(d.impl.show)
|
||||
}
|
||||
|
||||
func (d *OpenFileDialog) SetMessage(message string) *OpenFileDialog {
|
||||
|
|
@ -413,7 +411,7 @@ func (d *SaveFileDialog) PromptForSingleSelection() (string, error) {
|
|||
if d.impl == nil {
|
||||
d.impl = newSaveFileDialogImpl(d)
|
||||
}
|
||||
return d.impl.show()
|
||||
return invokeSyncWithResultAndError(d.impl.show)
|
||||
}
|
||||
|
||||
func (d *SaveFileDialog) SetButtonText(text string) *SaveFileDialog {
|
||||
|
|
|
|||
171
v3/pkg/application/dialogs_windows.go
Normal file
171
v3/pkg/application/dialogs_windows.go
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
//go:build windows
|
||||
|
||||
package application
|
||||
|
||||
func (m *windowsApp) showAboutDialog(title string, message string, icon []byte) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
type windowsDialog struct {
|
||||
dialog *MessageDialog
|
||||
|
||||
//dialogImpl unsafe.Pointer
|
||||
}
|
||||
|
||||
func (m *windowsDialog) show() {
|
||||
//
|
||||
//// Mac can only have 4 Buttons on a dialog
|
||||
//if len(m.dialog.Buttons) > 4 {
|
||||
// m.dialog.Buttons = m.dialog.Buttons[:4]
|
||||
//}
|
||||
//
|
||||
//if m.nsDialog != nil {
|
||||
// C.releaseDialog(m.nsDialog)
|
||||
//}
|
||||
//var title *C.char
|
||||
//if m.dialog.Title != "" {
|
||||
// title = C.CString(m.dialog.Title)
|
||||
//}
|
||||
//var message *C.char
|
||||
//if m.dialog.Message != "" {
|
||||
// message = C.CString(m.dialog.Message)
|
||||
//}
|
||||
//var iconData unsafe.Pointer
|
||||
//var iconLength C.int
|
||||
//if m.dialog.Icon != nil {
|
||||
// iconData = unsafe.Pointer(&m.dialog.Icon[0])
|
||||
// iconLength = C.int(len(m.dialog.Icon))
|
||||
//} else {
|
||||
// // if it's an error, use the application Icon
|
||||
// if m.dialog.DialogType == ErrorDialog {
|
||||
// iconData = unsafe.Pointer(&globalApplication.options.Icon[0])
|
||||
// iconLength = C.int(len(globalApplication.options.Icon))
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//alertType, ok := alertTypeMap[m.dialog.DialogType]
|
||||
//if !ok {
|
||||
// alertType = C.NSAlertStyleInformational
|
||||
//}
|
||||
//
|
||||
//m.nsDialog = C.createAlert(alertType, title, message, iconData, iconLength)
|
||||
//
|
||||
//// Reverse the Buttons so that the default is on the right
|
||||
//reversedButtons := make([]*Button, len(m.dialog.Buttons))
|
||||
//var count = 0
|
||||
//for i := len(m.dialog.Buttons) - 1; i >= 0; i-- {
|
||||
// button := m.dialog.Buttons[i]
|
||||
// C.alertAddButton(m.nsDialog, C.CString(button.Label), C.bool(button.IsDefault), C.bool(button.IsCancel))
|
||||
// reversedButtons[count] = m.dialog.Buttons[i]
|
||||
// count++
|
||||
//}
|
||||
//
|
||||
//buttonPressed := int(C.dialogRunModal(m.nsDialog))
|
||||
//if len(m.dialog.Buttons) > buttonPressed {
|
||||
// button := reversedButtons[buttonPressed]
|
||||
// if button.callback != nil {
|
||||
// button.callback()
|
||||
// }
|
||||
//}
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newDialogImpl(d *MessageDialog) *windowsDialog {
|
||||
return &windowsDialog{
|
||||
dialog: d,
|
||||
}
|
||||
}
|
||||
|
||||
type windowOpenFileDialog struct {
|
||||
dialog *OpenFileDialog
|
||||
}
|
||||
|
||||
func newOpenFileDialogImpl(d *OpenFileDialog) *windowOpenFileDialog {
|
||||
return &windowOpenFileDialog{
|
||||
dialog: d,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *windowOpenFileDialog) show() ([]string, error) {
|
||||
//openFileResponses[m.dialog.id] = make(chan string)
|
||||
//nsWindow := unsafe.Pointer(nil)
|
||||
//if m.dialog.window != nil {
|
||||
// // get NSWindow from window
|
||||
// nsWindow = m.dialog.window.impl.(*windowsWebviewWindow).nsWindow
|
||||
//}
|
||||
//
|
||||
//// Massage filter patterns into macOS format
|
||||
//// We iterate all filter patterns, tidy them up and then join them with a semicolon
|
||||
//// This should produce a single string of extensions like "png;jpg;gif"
|
||||
//var filterPatterns string
|
||||
//if len(m.dialog.filters) > 0 {
|
||||
// var allPatterns []string
|
||||
// for _, filter := range m.dialog.filters {
|
||||
// patternComponents := strings.Split(filter.Pattern, ";")
|
||||
// for i, component := range patternComponents {
|
||||
// filterPattern := strings.TrimSpace(component)
|
||||
// filterPattern = strings.TrimPrefix(filterPattern, "*.")
|
||||
// patternComponents[i] = filterPattern
|
||||
// }
|
||||
// allPatterns = append(allPatterns, strings.Join(patternComponents, ";"))
|
||||
// }
|
||||
// filterPatterns = strings.Join(allPatterns, ";")
|
||||
//}
|
||||
//
|
||||
//C.showOpenFileDialog(C.uint(m.dialog.id),
|
||||
// C.bool(m.dialog.canChooseFiles),
|
||||
// C.bool(m.dialog.canChooseDirectories),
|
||||
// C.bool(m.dialog.canCreateDirectories),
|
||||
// C.bool(m.dialog.showHiddenFiles),
|
||||
// C.bool(m.dialog.allowsMultipleSelection),
|
||||
// C.bool(m.dialog.resolvesAliases),
|
||||
// C.bool(m.dialog.hideExtension),
|
||||
// C.bool(m.dialog.treatsFilePackagesAsDirectories),
|
||||
// C.bool(m.dialog.allowsOtherFileTypes),
|
||||
// toCString(filterPatterns),
|
||||
// C.uint(len(filterPatterns)),
|
||||
// toCString(m.dialog.message),
|
||||
// toCString(m.dialog.directory),
|
||||
// toCString(m.dialog.buttonText),
|
||||
// nsWindow)
|
||||
//var result []string
|
||||
//for filename := range openFileResponses[m.dialog.id] {
|
||||
// result = append(result, filename)
|
||||
//}
|
||||
//return result, nil
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
type windowSaveFileDialog struct {
|
||||
dialog *SaveFileDialog
|
||||
}
|
||||
|
||||
func newSaveFileDialogImpl(d *SaveFileDialog) *windowSaveFileDialog {
|
||||
return &windowSaveFileDialog{
|
||||
dialog: d,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *windowSaveFileDialog) show() (string, error) {
|
||||
//saveFileResponses[m.dialog.id] = make(chan string)
|
||||
//nsWindow := unsafe.Pointer(nil)
|
||||
//if m.dialog.window != nil {
|
||||
// // get NSWindow from window
|
||||
// nsWindow = m.dialog.window.impl.(*macosWebviewWindow).nsWindow
|
||||
//}
|
||||
//C.showSaveFileDialog(C.uint(m.dialog.id),
|
||||
// C.bool(m.dialog.canCreateDirectories),
|
||||
// C.bool(m.dialog.showHiddenFiles),
|
||||
// C.bool(m.dialog.canSelectHiddenExtension),
|
||||
// C.bool(m.dialog.hideExtension),
|
||||
// C.bool(m.dialog.treatsFilePackagesAsDirectories),
|
||||
// C.bool(m.dialog.allowOtherFileTypes),
|
||||
// toCString(m.dialog.message),
|
||||
// toCString(m.dialog.directory),
|
||||
// toCString(m.dialog.buttonText),
|
||||
// toCString(m.dialog.filename),
|
||||
// nsWindow)
|
||||
//return <-saveFileResponses[m.dialog.id], nil
|
||||
panic("implement me")
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
20
v3/pkg/application/image.go
Normal file
20
v3/pkg/application/image.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package application
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
)
|
||||
|
||||
func pngToImage(data []byte) (*image.RGBA, error) {
|
||||
img, err := png.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bounds := img.Bounds()
|
||||
rgba := image.NewRGBA(bounds)
|
||||
draw.Draw(rgba, bounds, img, bounds.Min, draw.Src)
|
||||
return rgba, nil
|
||||
}
|
||||
126
v3/pkg/application/mainthread_windows.go
Normal file
126
v3/pkg/application/mainthread_windows.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
//go:build windows
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
"runtime"
|
||||
"sort"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
wmInvokeCallback uint32
|
||||
)
|
||||
|
||||
func init() {
|
||||
wmInvokeCallback = w32.RegisterWindowMessage(w32.MustStringToUTF16Ptr("WailsV0.InvokeCallback"))
|
||||
}
|
||||
|
||||
// initMainLoop must be called with the same OSThread that is used to call runMainLoop() later.
|
||||
func (m *windowsApp) initMainLoop() {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if m.mainThreadWindowHWND != 0 {
|
||||
panic("initMainLoop was already called")
|
||||
}
|
||||
|
||||
// We need a hidden window so we can PostMessage to it, if we don't use PostMessage for dispatching to a HWND
|
||||
// messages might get lost if a modal inner loop is being run.
|
||||
// We had this once in V2: https://github.com/wailsapp/wails/issues/969
|
||||
// See: https://devblogs.microsoft.com/oldnewthing/20050426-18/?p=35783
|
||||
// See also: https://learn.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues#creating-a-message-loop
|
||||
// > Because the system directs messages to individual windows in an application, a thread must create at least one window before starting its message loop.
|
||||
m.mainThreadWindowHWND = w32.CreateWindowEx(
|
||||
0,
|
||||
windowClassName,
|
||||
w32.MustStringToUTF16Ptr("__wails_hidden_mainthread"),
|
||||
w32.WS_DISABLED,
|
||||
w32.CW_USEDEFAULT,
|
||||
w32.CW_USEDEFAULT,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
w32.GetModuleHandle(""),
|
||||
nil)
|
||||
|
||||
m.mainThreadID, _ = w32.GetWindowThreadProcessId(m.mainThreadWindowHWND)
|
||||
}
|
||||
|
||||
func (m *windowsApp) runMainLoop() int {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if m.invokeRequired() {
|
||||
panic("invokeRequired for runMainLoop, the mainloop must be running on the same OSThread as the mainThreadWindow has been created on")
|
||||
}
|
||||
|
||||
msg := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{})))))
|
||||
defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(m)))
|
||||
|
||||
for w32.GetMessage(msg, 0, 0, 0) != 0 {
|
||||
w32.TranslateMessage(msg)
|
||||
w32.DispatchMessage(msg)
|
||||
}
|
||||
|
||||
return int(msg.WParam)
|
||||
}
|
||||
|
||||
func (m *windowsApp) dispatchOnMainThread(id uint) {
|
||||
mainThreadHWND := m.mainThreadWindowHWND
|
||||
if mainThreadHWND == 0 {
|
||||
panic("initMainLoop was not called")
|
||||
}
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if m.invokeRequired() {
|
||||
w32.PostMessage(mainThreadHWND, wmInvokeCallback, uintptr(id), 0)
|
||||
} else {
|
||||
mainThreadFunctionStoreLock.Lock()
|
||||
fn := mainThreadFunctionStore[id]
|
||||
delete(mainThreadFunctionStore, id)
|
||||
mainThreadFunctionStoreLock.Unlock()
|
||||
|
||||
if fn == nil {
|
||||
Fatal("dispatchOnMainThread called with invalid id: %v", id)
|
||||
}
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *windowsApp) invokeRequired() bool {
|
||||
mainThreadID := m.mainThreadID
|
||||
if mainThreadID == 0 {
|
||||
panic("initMainLoop was not called")
|
||||
}
|
||||
|
||||
return mainThreadID != w32.GetCurrentThreadId()
|
||||
}
|
||||
|
||||
func (m *windowsApp) invokeCallback(wParam, lParam uintptr) {
|
||||
// TODO: Should we invoke just one or all queued? In v2 we always invoked all pendings...
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
if m.invokeRequired() {
|
||||
panic("invokeCallback must always be called on the MainOSThread")
|
||||
}
|
||||
|
||||
mainThreadFunctionStoreLock.Lock()
|
||||
fnIDs := make([]uint, 0, len(mainThreadFunctionStore))
|
||||
for id := range mainThreadFunctionStore {
|
||||
fnIDs = append(fnIDs, id)
|
||||
}
|
||||
sort.Slice(fnIDs, func(i, j int) bool { return fnIDs[i] < fnIDs[j] })
|
||||
|
||||
fns := make([]func(), len(fnIDs))
|
||||
for i, id := range fnIDs {
|
||||
fns[i] = mainThreadFunctionStore[id]
|
||||
delete(mainThreadFunctionStore, id)
|
||||
}
|
||||
mainThreadFunctionStoreLock.Unlock()
|
||||
|
||||
for _, fn := range fns {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ func (m *Menu) Add(label string) *MenuItem {
|
|||
}
|
||||
|
||||
func (m *Menu) AddSeparator() {
|
||||
result := newMenuItemSeperator()
|
||||
result := newMenuItemSeparator()
|
||||
m.items = append(m.items, result)
|
||||
}
|
||||
|
||||
|
|
@ -96,14 +96,3 @@ func (m *Menu) setContextData(data *ContextMenuData) {
|
|||
func (a *App) NewMenu() *Menu {
|
||||
return &Menu{}
|
||||
}
|
||||
|
||||
func defaultApplicationMenu() *Menu {
|
||||
menu := NewMenu()
|
||||
menu.AddRole(AppMenu)
|
||||
menu.AddRole(FileMenu)
|
||||
menu.AddRole(EditMenu)
|
||||
menu.AddRole(ViewMenu)
|
||||
menu.AddRole(WindowMenu)
|
||||
menu.AddRole(HelpMenu)
|
||||
return menu
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,3 +103,14 @@ func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
func defaultApplicationMenu() *Menu {
|
||||
menu := NewMenu()
|
||||
menu.AddRole(AppMenu)
|
||||
menu.AddRole(FileMenu)
|
||||
menu.AddRole(EditMenu)
|
||||
menu.AddRole(ViewMenu)
|
||||
menu.AddRole(WindowMenu)
|
||||
menu.AddRole(HelpMenu)
|
||||
return menu
|
||||
}
|
||||
|
|
|
|||
121
v3/pkg/application/menu_windows.go
Normal file
121
v3/pkg/application/menu_windows.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
//go:build windows
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
)
|
||||
|
||||
type windowsMenu struct {
|
||||
menu *Menu
|
||||
|
||||
hWnd w32.HWND
|
||||
hMenu w32.HMENU
|
||||
currentMenuID int
|
||||
menuMapping map[int]*MenuItem
|
||||
checkboxItems []*Menu
|
||||
}
|
||||
|
||||
func newMenuImpl(menu *Menu) *windowsMenu {
|
||||
result := &windowsMenu{
|
||||
menu: menu,
|
||||
menuMapping: make(map[int]*MenuItem),
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (w *windowsMenu) update() {
|
||||
if w.hMenu != 0 {
|
||||
w32.DestroyMenu(w.hMenu)
|
||||
}
|
||||
w.hMenu = w32.NewPopupMenu()
|
||||
w.processMenu(w.hMenu, w.menu)
|
||||
}
|
||||
|
||||
func (w *windowsMenu) processMenu(parentMenu w32.HMENU, inputMenu *Menu) {
|
||||
for _, item := range inputMenu.items {
|
||||
if item.Hidden() {
|
||||
continue
|
||||
}
|
||||
w.currentMenuID++
|
||||
itemID := w.currentMenuID
|
||||
w.menuMapping[itemID] = item
|
||||
|
||||
flags := uint32(w32.MF_STRING)
|
||||
if item.disabled {
|
||||
flags = flags | w32.MF_GRAYED
|
||||
}
|
||||
if item.checked {
|
||||
flags = flags | w32.MF_CHECKED
|
||||
}
|
||||
if item.IsSeparator() {
|
||||
flags = flags | w32.MF_SEPARATOR
|
||||
}
|
||||
//
|
||||
//if item.IsCheckbox() {
|
||||
// w.checkboxItems[item] = append(w.checkboxItems[item], itemID)
|
||||
//}
|
||||
//if item.IsRadio() {
|
||||
// currentRadioGroup.Add(itemID, item)
|
||||
//} else {
|
||||
// if len(currentRadioGroup) > 0 {
|
||||
// for _, radioMember := range currentRadioGroup {
|
||||
// currentRadioGroup := currentRadioGroup
|
||||
// p.radioGroups[radioMember.MenuItem] = append(p.radioGroups[radioMember.MenuItem], ¤tRadioGroup)
|
||||
// }
|
||||
// currentRadioGroup = RadioGroup{}
|
||||
// }
|
||||
//}
|
||||
|
||||
if item.submenu != nil {
|
||||
flags = flags | w32.MF_POPUP
|
||||
newSubmenu := w32.CreateMenu()
|
||||
w.processMenu(newSubmenu, item.submenu)
|
||||
itemID = int(newSubmenu)
|
||||
}
|
||||
|
||||
var menuText = w32.MustStringToUTF16Ptr(item.Label())
|
||||
|
||||
w32.AppendMenu(parentMenu, flags, uintptr(itemID), menuText)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowsMenu) ShowAtCursor() {
|
||||
invokeSync(func() {
|
||||
x, y, ok := w32.GetCursorPos()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.ShowAt(x, y)
|
||||
})
|
||||
}
|
||||
|
||||
func (w *windowsMenu) ShowAt(x int, y int) {
|
||||
w.update()
|
||||
w32.TrackPopupMenuEx(w.hMenu,
|
||||
w32.TPM_LEFTALIGN,
|
||||
int32(x),
|
||||
int32(y),
|
||||
w.hWnd,
|
||||
nil)
|
||||
w32.PostMessage(w.hWnd, w32.WM_NULL, 0, 0)
|
||||
}
|
||||
|
||||
func (w *windowsMenu) ProcessCommand(cmdMsgID int) {
|
||||
item := w.menuMapping[cmdMsgID]
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
item.handleClick()
|
||||
}
|
||||
|
||||
func defaultApplicationMenu() *Menu {
|
||||
menu := NewMenu()
|
||||
menu.AddRole(FileMenu)
|
||||
menu.AddRole(EditMenu)
|
||||
menu.AddRole(ViewMenu)
|
||||
menu.AddRole(WindowMenu)
|
||||
menu.AddRole(HelpMenu)
|
||||
return menu
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ type menuItemImpl interface {
|
|||
setDisabled(disabled bool)
|
||||
setChecked(checked bool)
|
||||
setAccelerator(accelerator *accelerator)
|
||||
setHidden(hidden bool)
|
||||
}
|
||||
|
||||
type MenuItem struct {
|
||||
|
|
@ -46,6 +47,7 @@ type MenuItem struct {
|
|||
tooltip string
|
||||
disabled bool
|
||||
checked bool
|
||||
hidden bool
|
||||
submenu *Menu
|
||||
callback func(*Context)
|
||||
itemType menuItemType
|
||||
|
|
@ -67,7 +69,7 @@ func newMenuItem(label string) *MenuItem {
|
|||
return result
|
||||
}
|
||||
|
||||
func newMenuItemSeperator() *MenuItem {
|
||||
func newMenuItemSeparator() *MenuItem {
|
||||
result := &MenuItem{
|
||||
itemType: separator,
|
||||
}
|
||||
|
|
@ -173,6 +175,8 @@ func newRole(role Role) *MenuItem {
|
|||
return newMinimizeMenuItem()
|
||||
case Zoom:
|
||||
return newZoomMenuItem()
|
||||
case FullScreen:
|
||||
return newFullScreenMenuItem()
|
||||
|
||||
default:
|
||||
println("No support for role:", role)
|
||||
|
|
@ -255,10 +259,38 @@ func (m *MenuItem) SetChecked(checked bool) *MenuItem {
|
|||
return m
|
||||
}
|
||||
|
||||
func (m *MenuItem) SetHidden(hidden bool) *MenuItem {
|
||||
m.hidden = hidden
|
||||
if m.impl != nil {
|
||||
m.impl.setHidden(m.hidden)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MenuItem) Checked() bool {
|
||||
return m.checked
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsSeparator() bool {
|
||||
return m.itemType == separator
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsSubmenu() bool {
|
||||
return m.itemType == submenu
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsCheckbox() bool {
|
||||
return m.itemType == checkbox
|
||||
}
|
||||
|
||||
func (m *MenuItem) IsRadio() bool {
|
||||
return m.itemType == radio
|
||||
}
|
||||
|
||||
func (m *MenuItem) Hidden() bool {
|
||||
return m.hidden
|
||||
}
|
||||
|
||||
func (m *MenuItem) OnClick(f func(*Context)) *MenuItem {
|
||||
m.callback = f
|
||||
return m
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
//go:build darwin
|
||||
|
||||
package application
|
||||
|
||||
/*
|
||||
|
|
@ -329,29 +331,29 @@ import (
|
|||
"unsafe"
|
||||
)
|
||||
|
||||
type macosMenuItem struct {
|
||||
type windowsMenuItem struct {
|
||||
menuItem *MenuItem
|
||||
|
||||
nsMenuItem unsafe.Pointer
|
||||
}
|
||||
|
||||
func (m macosMenuItem) setTooltip(tooltip string) {
|
||||
func (m windowsMenuItem) setTooltip(tooltip string) {
|
||||
C.setMenuItemTooltip(m.nsMenuItem, C.CString(tooltip))
|
||||
}
|
||||
|
||||
func (m macosMenuItem) setLabel(s string) {
|
||||
func (m windowsMenuItem) setLabel(s string) {
|
||||
C.setMenuItemLabel(m.nsMenuItem, C.CString(s))
|
||||
}
|
||||
|
||||
func (m macosMenuItem) setDisabled(disabled bool) {
|
||||
func (m windowsMenuItem) setDisabled(disabled bool) {
|
||||
C.setMenuItemDisabled(m.nsMenuItem, C.bool(disabled))
|
||||
}
|
||||
|
||||
func (m macosMenuItem) setChecked(checked bool) {
|
||||
func (m windowsMenuItem) setChecked(checked bool) {
|
||||
C.setMenuItemChecked(m.nsMenuItem, C.bool(checked))
|
||||
}
|
||||
|
||||
func (m macosMenuItem) setAccelerator(accelerator *accelerator) {
|
||||
func (m windowsMenuItem) setAccelerator(accelerator *accelerator) {
|
||||
// Set the keyboard shortcut of the menu item
|
||||
var modifier C.int
|
||||
var key *C.char
|
||||
|
|
@ -364,8 +366,8 @@ func (m macosMenuItem) setAccelerator(accelerator *accelerator) {
|
|||
C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier)
|
||||
}
|
||||
|
||||
func newMenuItemImpl(item *MenuItem) *macosMenuItem {
|
||||
result := &macosMenuItem{
|
||||
func newMenuItemImpl(item *MenuItem) *windowsMenuItem {
|
||||
result := &windowsMenuItem{
|
||||
menuItem: item,
|
||||
}
|
||||
|
||||
|
|
@ -604,7 +606,7 @@ func newMinimizeMenuItem() *MenuItem {
|
|||
OnClick(func(ctx *Context) {
|
||||
currentWindow := globalApplication.CurrentWindow()
|
||||
if currentWindow != nil {
|
||||
currentWindow.Minimize()
|
||||
currentWindow.Minimise()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -618,3 +620,13 @@ func newZoomMenuItem() *MenuItem {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func newFullScreenMenuItem() *MenuItem {
|
||||
return newMenuItem("Fullscreen").
|
||||
OnClick(func(ctx *Context) {
|
||||
currentWindow := globalApplication.CurrentWindow()
|
||||
if currentWindow != nil {
|
||||
currentWindow.Fullscreen()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
183
v3/pkg/application/menuitem_windows.go
Normal file
183
v3/pkg/application/menuitem_windows.go
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
//go:build windows
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type windowsMenuItem struct {
|
||||
menuItem *MenuItem
|
||||
|
||||
menuItemImpl unsafe.Pointer
|
||||
}
|
||||
|
||||
func (m windowsMenuItem) setTooltip(tooltip string) {
|
||||
//C.setMenuItemTooltip(m.nsMenuItem, C.CString(tooltip))
|
||||
}
|
||||
|
||||
func (m windowsMenuItem) setLabel(s string) {
|
||||
//C.setMenuItemLabel(m.nsMenuItem, C.CString(s))
|
||||
}
|
||||
|
||||
func (m windowsMenuItem) setDisabled(disabled bool) {
|
||||
//C.setMenuItemDisabled(m.nsMenuItem, C.bool(disabled))
|
||||
}
|
||||
|
||||
func (m windowsMenuItem) setChecked(checked bool) {
|
||||
//C.setMenuItemChecked(m.nsMenuItem, C.bool(checked))
|
||||
}
|
||||
|
||||
func (m windowsMenuItem) setAccelerator(accelerator *accelerator) {
|
||||
//// Set the keyboard shortcut of the menu item
|
||||
//var modifier C.int
|
||||
//var key *C.char
|
||||
//if accelerator != nil {
|
||||
// modifier = C.int(toMacModifier(accelerator.Modifiers))
|
||||
// key = C.CString(accelerator.Key)
|
||||
//}
|
||||
//
|
||||
//// Convert the key to a string
|
||||
//C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier)
|
||||
}
|
||||
|
||||
func newMenuItemImpl(item *MenuItem) *windowsMenuItem {
|
||||
result := &windowsMenuItem{
|
||||
menuItem: item,
|
||||
}
|
||||
//
|
||||
//switch item.itemType {
|
||||
//case text, checkbox, submenu, radio:
|
||||
// result.nsMenuItem = unsafe.Pointer(C.newMenuItem(C.uint(item.id), C.CString(item.label), C.bool(item.disabled), C.CString(item.tooltip)))
|
||||
// if item.itemType == checkbox || item.itemType == radio {
|
||||
// C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked))
|
||||
// }
|
||||
// if item.accelerator != nil {
|
||||
// result.setAccelerator(item.accelerator)
|
||||
// }
|
||||
//default:
|
||||
// panic("WTF")
|
||||
//}
|
||||
return result
|
||||
}
|
||||
|
||||
func newSpeechMenu() *MenuItem {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newHideMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newHideOthersMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newUnhideMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newUndoMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
// newRedoMenuItem creates a new menu item for redoing the last action
|
||||
func newRedoMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newCutMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newCopyMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newPasteMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newPasteAndMatchStyleMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newDeleteMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newQuitMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newSelectAllMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newAboutMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newCloseMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newReloadMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newForceReloadMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newToggleFullscreenMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newToggleDevToolsMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newZoomResetMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newZoomInMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newZoomOutMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
|
||||
}
|
||||
|
||||
func newMinimizeMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newZoomMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newFullScreenMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ func (m *MessageProcessor) processWindowMethod(method string, rw http.ResponseWr
|
|||
window.UnFullscreen()
|
||||
m.ok(rw)
|
||||
case "Minimise":
|
||||
window.Minimize()
|
||||
window.Minimise()
|
||||
m.ok(rw)
|
||||
case "UnMinimise":
|
||||
window.UnMinimise()
|
||||
|
|
@ -102,7 +102,7 @@ func (m *MessageProcessor) processWindowMethod(method string, rw http.ResponseWr
|
|||
m.Error("Invalid SetBackgroundColour Message: 'a' value required")
|
||||
return
|
||||
}
|
||||
window.SetBackgroundColour(&RGBA{
|
||||
window.SetBackgroundColour(RGBA{
|
||||
Red: *r,
|
||||
Green: *g,
|
||||
Blue: *b,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ type Options struct {
|
|||
Description string
|
||||
Icon []byte
|
||||
Mac MacOptions
|
||||
Windows WindowsApplicationOptions
|
||||
Bind []any
|
||||
Logger struct {
|
||||
Silent bool
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ const (
|
|||
type WebviewWindowOptions struct {
|
||||
Name string
|
||||
Title string
|
||||
Width, Height int
|
||||
Width int
|
||||
Height int
|
||||
AlwaysOnTop bool
|
||||
URL string
|
||||
DisableResize bool
|
||||
|
|
@ -22,8 +23,9 @@ type WebviewWindowOptions struct {
|
|||
MaxWidth int
|
||||
MaxHeight int
|
||||
StartState WindowState
|
||||
Mac MacWindow
|
||||
BackgroundColour *RGBA
|
||||
Centered bool
|
||||
BackgroundType BackgroundType
|
||||
BackgroundColour RGBA
|
||||
HTML string
|
||||
JS string
|
||||
CSS string
|
||||
|
|
@ -35,6 +37,9 @@ type WebviewWindowOptions struct {
|
|||
EnableFraudulentWebsiteWarnings bool
|
||||
Zoom float64
|
||||
EnableDragAndDrop bool
|
||||
Mac MacWindow
|
||||
Windows WindowsWindow
|
||||
Focused bool
|
||||
}
|
||||
|
||||
var WebviewWindowDefaults = &WebviewWindowOptions{
|
||||
|
|
@ -42,8 +47,22 @@ var WebviewWindowDefaults = &WebviewWindowOptions{
|
|||
Width: 800,
|
||||
Height: 600,
|
||||
URL: "",
|
||||
BackgroundColour: RGBA{
|
||||
Red: 255,
|
||||
Green: 255,
|
||||
Blue: 255,
|
||||
Alpha: 255,
|
||||
},
|
||||
}
|
||||
|
||||
type RGBA struct {
|
||||
Red, Green, Blue, Alpha uint8
|
||||
}
|
||||
|
||||
type BackgroundType int
|
||||
|
||||
const (
|
||||
BackgroundTypeSolid BackgroundType = iota
|
||||
BackgroundTypeTransparent
|
||||
BackgroundTypeTranslucent
|
||||
)
|
||||
|
|
|
|||
66
v3/pkg/application/options_win.go
Normal file
66
v3/pkg/application/options_win.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package application
|
||||
|
||||
type WindowsApplicationOptions struct {
|
||||
// WndProcInterceptor is a function that will be called for every message sent in the application.
|
||||
// Use this to hook into the main message loop. This is useful for handling custom window messages.
|
||||
// If `shouldReturn` is `true` then `returnCode` will be returned by the main message loop.
|
||||
// If `shouldReturn` is `false` then returnCode will be ignored and the message will be processed by the main message loop.
|
||||
WndProcInterceptor func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnCode uintptr, shouldReturn bool)
|
||||
}
|
||||
|
||||
type BackdropType int32
|
||||
|
||||
const (
|
||||
Auto BackdropType = 0
|
||||
None BackdropType = 1
|
||||
Mica BackdropType = 2
|
||||
Acrylic BackdropType = 3
|
||||
Tabbed BackdropType = 4
|
||||
)
|
||||
|
||||
type WindowsWindow struct {
|
||||
// Select the type of translucent backdrop. Requires Windows 11 22621 or later.
|
||||
BackdropType BackdropType
|
||||
// Disable the icon in the titlebar
|
||||
DisableIcon bool
|
||||
// Theme. Defaults to SystemDefault which will use whatever the system theme is. The application will follow system theme changes.
|
||||
Theme Theme
|
||||
// Custom colours for dark/light mode
|
||||
CustomTheme *ThemeSettings
|
||||
|
||||
// Disable all window decorations in Frameless mode, which means no "Aero Shadow" and no "Rounded Corner" will be shown.
|
||||
// "Rounded Corners" are only available on Windows 11.
|
||||
DisableFramelessWindowDecorations bool
|
||||
|
||||
// WindowMask is used to set the window shape. Use a PNG with an alpha channel to create a custom shape.
|
||||
WindowMask []byte
|
||||
WindowMaskDraggable bool
|
||||
}
|
||||
|
||||
type Theme int
|
||||
|
||||
const (
|
||||
// SystemDefault will use whatever the system theme is. The application will follow system theme changes.
|
||||
SystemDefault Theme = 0
|
||||
// Dark Mode
|
||||
Dark Theme = 1
|
||||
// Light Mode
|
||||
Light Theme = 2
|
||||
)
|
||||
|
||||
// ThemeSettings defines custom colours to use in dark or light mode.
|
||||
// They may be set using the hex values: 0x00BBGGRR
|
||||
type ThemeSettings struct {
|
||||
DarkModeTitleBar int32
|
||||
DarkModeTitleBarInactive int32
|
||||
DarkModeTitleText int32
|
||||
DarkModeTitleTextInactive int32
|
||||
DarkModeBorder int32
|
||||
DarkModeBorderInactive int32
|
||||
LightModeTitleBar int32
|
||||
LightModeTitleBarInactive int32
|
||||
LightModeTitleText int32
|
||||
LightModeTitleTextInactive int32
|
||||
LightModeBorder int32
|
||||
LightModeBorderInactive int32
|
||||
}
|
||||
|
|
@ -42,8 +42,9 @@ const (
|
|||
ZoomOut Role = iota
|
||||
ToggleFullscreen Role = iota
|
||||
|
||||
Minimize Role = iota
|
||||
Zoom Role = iota
|
||||
Minimize Role = iota
|
||||
Zoom Role = iota
|
||||
FullScreen Role = iota
|
||||
//Front Role = iota
|
||||
//WindowRole Role = iota
|
||||
|
||||
|
|
@ -132,6 +133,12 @@ func newWindowMenu() *MenuItem {
|
|||
menu := NewMenu()
|
||||
menu.AddRole(Minimize)
|
||||
menu.AddRole(Zoom)
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddSeparator()
|
||||
menu.AddRole(FullScreen)
|
||||
} else {
|
||||
menu.AddRole(Close)
|
||||
}
|
||||
subMenu := newSubMenuItem("Window")
|
||||
subMenu.submenu = menu
|
||||
return subMenu
|
||||
|
|
|
|||
|
|
@ -128,12 +128,12 @@ func cScreenToScreen(screen C.Screen) *Screen {
|
|||
}
|
||||
}
|
||||
|
||||
func getPrimaryScreen() (*Screen, error) {
|
||||
func (m *macosApp) getPrimaryScreen() (*Screen, error) {
|
||||
cScreen := C.GetPrimaryScreen()
|
||||
return cScreenToScreen(cScreen), nil
|
||||
}
|
||||
|
||||
func getScreens() ([]*Screen, error) {
|
||||
func (m *macosApp) getScreens() ([]*Screen, error) {
|
||||
cScreens := C.getAllScreens()
|
||||
defer C.free(unsafe.Pointer(cScreens))
|
||||
numScreens := int(C.GetNumScreens())
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue