Compare commits

...

49 commits

Author SHA1 Message Date
Travis McLane
d14e4048aa [dialogs] remove default title
without this removal a user would have to call `SetTitle("")` in order to
erase the default if they desire a title-less dialog.
2023-05-10 10:57:25 -05:00
Lea Anthony
7fd627f169
[v3 windows] initial systray support 2023-05-10 19:35:40 +10:00
Lea Anthony
b91468b6f2 [v3 mac] Update api. New template icon. 2023-05-09 21:55:17 +10:00
Lea Anthony
0b9cd4be5d
[v3 windows] update status 2023-05-09 21:47:22 +10:00
Lea Anthony
4c587ee1b8
[v3 windows] New icons 2023-05-09 19:53:01 +10:00
Lea Anthony
19e1e8b8a6
[v3 windows] Move icons to own package, systray dark mode icon, window.Focus(), 2023-05-09 19:52:33 +10:00
Lea Anthony
67e9522c67
[v3 windows] Rename systray callback handlers 2023-05-08 20:16:25 +10:00
Lea Anthony
a829b38a34
[v3 windows] Dialogs to use invokeSync 2023-05-08 20:11:51 +10:00
Lea Anthony
c8dae94b5b
[v3 windows] Systray callback handlers 2023-05-08 19:43:58 +10:00
stffabi
87267758ac [v3 windows] Fix wndproc default case with WMMessageToString 2023-05-07 22:17:43 +02:00
stffabi
5f72df81fb [v3 windows] Do not disable WndProc messaging for systray when updating icon 2023-05-07 21:40:40 +02:00
Lea Anthony
1ed270fe05
[v3 windows] Fix systray icon size 2023-05-07 20:19:37 +10:00
Lea Anthony
19a654a2b1
[v3 windows] Initial systray support 2023-05-07 19:13:07 +10:00
Lea Anthony
f9bbc11711
[v3] Use invokeSync for systray methods 2023-05-07 10:34:08 +10:00
Lea Anthony
42b1807c36
[v3 windows] Support irregular shaped windows. Centered option. 2023-05-06 20:22:28 +10:00
Lea Anthony
647982de1a
[v3 windows] Support irregular shaped windows 2023-05-06 15:05:00 +10:00
Lea Anthony
f5557c612a
Merge remote-tracking branch 'origin/v3-alpha' into v3-alpha 2023-05-05 06:41:04 +10:00
stffabi
f4749db8b3 [v3] Add some missing methods for darwin and windows 2023-05-02 21:56:40 +02:00
Travis McLane
86a1de6788 [w32] move windows specific code to impl file 2023-05-02 10:52:21 -05:00
Travis McLane
792c5e2d95 [w32] add missing build constraint 2023-05-02 10:52:09 -05:00
Lea Anthony
6758580be9
[v3 windows] Add frameless resize 2023-05-02 23:18:22 +10:00
stffabi
6f246eed4a [v3 windows] Add HiDPI awareness 2023-05-02 09:45:21 +02:00
stffabi
fc3725d3f4 [v3 windows] Add frameless support 2023-05-02 09:29:47 +02:00
Lea Anthony
00c6f0dfdb
[v3 windows] Implement getScreen 2023-05-01 21:11:40 +10:00
Lea Anthony
cf7b4e2458
[v3] Ensure impl calls from WebvieWindow are on the main thread. Support size. 2023-05-01 20:28:46 +10:00
Lea Anthony
f682e44367
[v3] Fix examples 2023-05-01 18:24:24 +10:00
Lea Anthony
9d1f86c410
[v3] Update examples to use correct options. 2023-05-01 18:21:22 +10:00
Lea Anthony
dac281ac32
[v3] Change WebviewWindow options to be a value, not a pointer. Support Un/Fullscreen. Remove main thread switching. Use parent options instead of local variables. 2023-05-01 18:20:28 +10:00
Lea Anthony
29a58086a3
[v3 Windows] Support application hide/show. Add WebviewWindow.IsVisible(). 2023-05-01 11:34:06 +10:00
Lea Anthony
cb8eb755a7
[v3 Windows] Support setMin/MaxSize, setPosition 2023-05-01 10:52:46 +10:00
Lea Anthony
1e8fc29ee4
[v3 Windows] Add Support for SetTitle, Center, Un/Minimise/Maximise, IsMin/Maximised, IsNormal, Show/Hide 2023-04-30 20:55:51 +10:00
Lea Anthony
9a05b49e3d
[v3 Breaking Change] Add NativeWindowHandle method to WebviewWindow. 2023-04-30 10:17:03 +10:00
Lea Anthony
79f8d92084
[v3 windows] Moved w32 from internal to pkg so it may be used by applications 2023-04-30 10:02:00 +10:00
Lea Anthony
6e56542586
[v3 windows] Add WndProcInterceptor for custom message processing 2023-04-30 09:49:50 +10:00
Lea Anthony
c53443b62b
[v3 windows] Rename options_windows.go -> options_win.go 2023-04-30 09:10:00 +10:00
Lea Anthony
1128662c89
[v3 windows] Add APM Events 2023-04-29 20:33:33 +10:00
Lea Anthony
676787417f
[v3] Update application.On and window.On to return functions that unregister the listener. WebviewWindow.onApplicationEvent is a helper which will manage the unregistering for you on window destroy. 2023-04-29 19:39:05 +10:00
Lea Anthony
57422dccf3
[windows] Split out wndProc. Generate windows events, support per-window themes 2023-04-29 12:14:12 +10:00
Lea Anthony
7f3f51e36b
[windows] Support AlwaysOnTop, EnableResize at runtime. Added Solid/Transparent/Translucent options. 2023-04-28 21:11:49 +10:00
stffabi
ef184ec8bf [v3, windows] Add MainThread dispatching and fixes the blocking window 2023-04-26 21:06:54 +02:00
Lea Anthony
9bfe3094dd
[windows] WIP 2023-04-26 21:07:04 +10:00
stffabi
3547b4d010
[v2, darwin] Add some missing default shortcuts (#2586)
* [v2, darwin] Add "Hide, Hide Others, Show All“ to appmenu

This also includes shortcuts support for those commands.
Arrange the menu items in the well known MacOS order.

* [v2, darwin] Add Window menu with well known shortcuts Minimize, Full-Screen and Zoom.
2023-04-26 21:07:04 +10:00
stffabi
1222e3aa1b
[v2, dev] Use custom schemes for in-app dev mode (#2610)
This fixes some long-standing inconsistencies between
dev mode builds and production builds but is a breaking
change. Dev mode uses custom scheme for Vite versions >= 3.0.0
and for older it still behaves in the old way.
2023-04-26 21:07:03 +10:00
stffabi
cff3ee5079
[assetServer] Improve release/close handling of webview requests (#2612) 2023-04-26 21:07:03 +10:00
Travis McLane
c91aa462aa
[darwin] add getPrimaryScreen/getScreens to impl (#2618) 2023-04-24 09:34:44 +10:00
Lea Anthony
62b3775e2f
Fix module path for non-modified repo 2023-04-19 08:22:58 +10:00
Lea Anthony
54bf8c1142
[windows] Initial commit 2023-04-18 21:27:09 +10:00
Lea Anthony
c4f613e4c5
[windows] Fix paths for wails init 2023-04-18 20:41:59 +10:00
Lea Anthony
a66d9ab0b1
Intial STATUS.md commit 2023-04-18 18:53:39 +10:00
152 changed files with 13154 additions and 529 deletions

View file

@ -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

View file

@ -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
}

View file

@ -8,6 +8,6 @@
"preview": "vite preview"
},
"devDependencies": {
"vite": "^2.9.9"
"vite": "^3.0.7"
}
}

View file

@ -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
}

View file

@ -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 */

View file

@ -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;
}
}

View file

@ -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() {

View file

@ -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()
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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() {}

View file

@ -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
}

View file

@ -13,6 +13,5 @@ type Request interface {
Response() ResponseWriter
AddRef() error
Release() error
Close() error
}

View file

@ -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 {

View 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
}

View file

@ -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
}

View file

@ -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()
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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.)

View file

@ -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...)
}
}
}

View file

@ -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"
}
}

View file

@ -11,6 +11,6 @@
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"svelte": "^3.49.0",
"vite": "^3.0.0"
"vite": "^3.0.7"
}
}

View file

@ -9,6 +9,6 @@
},
"devDependencies": {
"typescript": "^4.5.4",
"vite": "^2.9.9"
"vite": "^3.0.7"
}
}

View file

@ -8,6 +8,6 @@
"preview": "vite preview"
},
"devDependencies": {
"vite": "^2.9.9"
"vite": "^3.0.7"
}
}

319
v3/STATUS.md Normal file
View 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

View file

@ -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

View file

@ -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.

View file

@ -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 => ../..

View file

@ -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=

View file

@ -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 {

View file

@ -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,

View file

@ -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,

View file

@ -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()
})

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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 {

View file

@ -24,7 +24,7 @@ func main() {
},
})
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Screen Demo",
Width: 800,
Height: 600,

View file

@ -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 {

View file

@ -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,

View file

@ -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).

View file

@ -24,7 +24,7 @@ func main() {
},
})
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Wails ML Demo",
Width: 800,
Height: 600,

View file

@ -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

View file

@ -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=

View file

@ -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
}

View file

@ -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)
}

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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}}

View file

@ -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{

View file

@ -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
}

View file

@ -137,6 +137,10 @@ type macosApp struct {
parent *App
}
func getNativeApplication() *macosApp {
return globalApplication.impl.(*macosApp)
}
func (m *macosApp) hide() {
C.hide()
}

View 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
}

View file

@ -1,7 +1,5 @@
package application
import "C"
type clipboardImpl interface {
setText(text string) bool
text() string

View 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{}
}

View file

@ -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 {

View 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

View 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
}

View 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()
}
}

View file

@ -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
}

View file

@ -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
}

View 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], &currentRadioGroup)
// }
// 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
}

View file

@ -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

View file

@ -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()
}
})
}

View 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")
}

View file

@ -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,

View file

@ -12,6 +12,7 @@ type Options struct {
Description string
Icon []byte
Mac MacOptions
Windows WindowsApplicationOptions
Bind []any
Logger struct {
Silent bool

View file

@ -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
)

View 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
}

View file

@ -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

View file

@ -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