mirror of
https://github.com/wavetermdev/wails.git
synced 2026-04-22 15:26:15 -07:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d14e4048aa | |||
| 7fd627f169 | |||
| b91468b6f2 | |||
| 0b9cd4be5d | |||
| 4c587ee1b8 | |||
| 19e1e8b8a6 | |||
| 67e9522c67 | |||
| a829b38a34 | |||
| c8dae94b5b | |||
| 87267758ac | |||
| 5f72df81fb | |||
| 1ed270fe05 | |||
| 19a654a2b1 | |||
| f9bbc11711 | |||
| 42b1807c36 | |||
| 647982de1a | |||
| f5557c612a | |||
| f4749db8b3 | |||
| 86a1de6788 | |||
| 792c5e2d95 | |||
| 6758580be9 | |||
| 6f246eed4a | |||
| fc3725d3f4 | |||
| 00c6f0dfdb | |||
| cf7b4e2458 | |||
| f682e44367 | |||
| 9d1f86c410 | |||
| dac281ac32 | |||
| 29a58086a3 | |||
| cb8eb755a7 | |||
| 1e8fc29ee4 | |||
| 9a05b49e3d | |||
| 79f8d92084 | |||
| 6e56542586 | |||
| c53443b62b | |||
| 1128662c89 | |||
| 676787417f | |||
| 57422dccf3 | |||
| 7f3f51e36b | |||
| ef184ec8bf | |||
| 9bfe3094dd | |||
| 3547b4d010 | |||
| 1222e3aa1b | |||
| cff3ee5079 | |||
| c91aa462aa | |||
| 62b3775e2f | |||
| 54bf8c1142 | |||
| c4f613e4c5 | |||
| a66d9ab0b1 | |||
| c7e5608a60 | |||
| d8d4997532 | |||
| a1cc41c926 | |||
| 767bfcff89 | |||
| 46bcce7566 | |||
| c4609efe01 | |||
| cc6518f984 | |||
| 69d3a3f639 | |||
| 489689b477 | |||
| eaca3d06f6 | |||
| b302064f2f | |||
| 67063d419c | |||
| 03cef1887f | |||
| 929593c5b8 | |||
| 57e718af68 | |||
| f96aab0d44 | |||
| 140e3b5947 | |||
| a8339c9e93 |
@@ -1,29 +0,0 @@
|
||||
name: Generate Contributors Image
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 1 * * 0' # At 01:00 on Sunday.
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
contributors:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'wailsapp/wails'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: jaywcjlove/github-action-contributors@main
|
||||
id: contributors
|
||||
with:
|
||||
filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\])
|
||||
avatarSize: 100 # Set the avatar size.
|
||||
|
||||
- name: Modify htmlTable credits.md
|
||||
uses: jaywcjlove/github-action-modify-file-content@main
|
||||
with:
|
||||
openDelimiter: ' <!--GAMFC_DELIMITER-->'
|
||||
closeDelimiter: '<!--GAMFC_DELIMITER-END-->'
|
||||
path: website/src/pages/credits.mdx
|
||||
body: '${{steps.contributors.outputs.htmlTable}}'
|
||||
ref: chore/update-contributors
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Label Sponsors
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
build:
|
||||
name: is-sponsor-label
|
||||
if: github.repository == 'wailsapp/wails'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: JasonEtco/is-sponsor-label-action@v1
|
||||
with:
|
||||
label: Sponsor ❤️
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/flags"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
|
||||
|
||||
@@ -36,6 +37,10 @@ import (
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
)
|
||||
|
||||
const (
|
||||
viteMinVersion = "v3.0.0"
|
||||
)
|
||||
|
||||
func sliceToMap(input []string) map[string]struct{} {
|
||||
result := map[string]struct{}{}
|
||||
for _, value := range input {
|
||||
@@ -88,10 +93,11 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
||||
buildOptions.IgnoreApplication = false
|
||||
}
|
||||
|
||||
legacyUseDevServerInsteadofCustomScheme := false
|
||||
// frontend:dev:watcher command.
|
||||
frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery()
|
||||
if command := projectConfig.DevWatcherCommand; command != "" {
|
||||
closer, devServerURL, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
|
||||
closer, devServerURL, devServerViteVersion, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -100,6 +106,12 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
||||
f.FrontendDevServerURL = devServerURL
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if devServerViteVersion != "" && semver.Compare(devServerViteVersion, viteMinVersion) < 0 {
|
||||
logutils.LogRed("Please upgrade your Vite Server to at least '%s' future Wails versions will require at least Vite '%s'", viteMinVersion, viteMinVersion)
|
||||
time.Sleep(3 * time.Second)
|
||||
legacyUseDevServerInsteadofCustomScheme = true
|
||||
}
|
||||
} else if frontendDevAutoDiscovery {
|
||||
return fmt.Errorf("unable to auto discover frontend:dev:serverUrl without a frontend:dev:watcher command, please either set frontend:dev:watcher or remove the auto discovery from frontend:dev:serverUrl")
|
||||
}
|
||||
@@ -107,7 +119,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
||||
// Do initial build but only for the application.
|
||||
logger.Println("Building application for development...")
|
||||
buildOptions.IgnoreFrontend = true
|
||||
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel)
|
||||
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
|
||||
buildOptions.IgnoreFrontend = ignoreFrontend || f.FrontendDevServerURL != ""
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -153,7 +165,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
||||
}()
|
||||
|
||||
// Watch for changes and trigger restartApp()
|
||||
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL())
|
||||
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL(), legacyUseDevServerInsteadofCustomScheme)
|
||||
|
||||
// Kill the current program if running and remove dev binary
|
||||
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
|
||||
@@ -202,7 +214,7 @@ func runCommand(dir string, exitOnError bool, command string, args ...string) er
|
||||
}
|
||||
|
||||
// runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev`
|
||||
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, error) {
|
||||
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, string, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
scanner := NewStdoutScanner()
|
||||
cmdSlice := strings.Split(devCommand, " ")
|
||||
@@ -214,7 +226,7 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
cancel()
|
||||
return nil, "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
|
||||
return nil, "", "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
|
||||
}
|
||||
|
||||
var viteServerURL string
|
||||
@@ -224,10 +236,19 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
||||
viteServerURL = serverURL
|
||||
case <-time.After(time.Second * 10):
|
||||
cancel()
|
||||
return nil, "", errors.New("failed to find Vite server URL")
|
||||
return nil, "", "", errors.New("failed to find Vite server URL")
|
||||
}
|
||||
}
|
||||
|
||||
viteVersion := ""
|
||||
select {
|
||||
case version := <-scanner.ViteServerVersionC:
|
||||
viteVersion = version
|
||||
|
||||
case <-time.After(time.Second * 5):
|
||||
// That's fine, then most probably it was not vite that was running
|
||||
}
|
||||
|
||||
logutils.LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
@@ -255,11 +276,11 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
||||
}
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}, viteServerURL, nil
|
||||
}, viteServerURL, viteVersion, nil
|
||||
}
|
||||
|
||||
// restartApp does the actual rebuilding of the application when files change
|
||||
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int) (*process.Process, string, error) {
|
||||
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int, legacyUseDevServerInsteadofCustomScheme bool) (*process.Process, string, error) {
|
||||
|
||||
appBinary, err := build.Build(buildOptions)
|
||||
println()
|
||||
@@ -297,6 +318,9 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
|
||||
os.Setenv("assetdir", f.AssetDir)
|
||||
os.Setenv("devserver", f.DevServer)
|
||||
os.Setenv("frontenddevserverurl", f.FrontendDevServerURL)
|
||||
if legacyUseDevServerInsteadofCustomScheme {
|
||||
os.Setenv("legacyusedevsererinsteadofcustomscheme", "true")
|
||||
}
|
||||
|
||||
// Start up new binary with correct args
|
||||
newProcess := process.NewProcess(appBinary, args...)
|
||||
@@ -316,7 +340,7 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
|
||||
}
|
||||
|
||||
// doWatcherLoop is the main watch loop that runs while dev is active
|
||||
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) *process.Process {
|
||||
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL, legacyUseDevServerInsteadofCustomScheme bool) *process.Process {
|
||||
// Main Loop
|
||||
var extensionsThatTriggerARebuild = sliceToMap(strings.Split(f.Extensions, ","))
|
||||
var dirsThatTriggerAReload []string
|
||||
@@ -422,7 +446,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
|
||||
rebuild = false
|
||||
logutils.LogGreen("[Rebuild triggered] files updated")
|
||||
// Try and build the app
|
||||
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel)
|
||||
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
|
||||
if err != nil {
|
||||
logutils.LogRed("Error during build: %s", err.Error())
|
||||
continue
|
||||
|
||||
@@ -2,30 +2,47 @@ package dev
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// stdoutScanner acts as a stdout target that will scan the incoming
|
||||
// data to find out the vite server url
|
||||
type stdoutScanner struct {
|
||||
ViteServerURLChan chan string
|
||||
ViteServerURLChan chan string
|
||||
ViteServerVersionC chan string
|
||||
versionDetected bool
|
||||
}
|
||||
|
||||
// NewStdoutScanner creates a new stdoutScanner
|
||||
func NewStdoutScanner() *stdoutScanner {
|
||||
return &stdoutScanner{
|
||||
ViteServerURLChan: make(chan string, 2),
|
||||
ViteServerURLChan: make(chan string, 2),
|
||||
ViteServerVersionC: make(chan string, 2),
|
||||
}
|
||||
}
|
||||
|
||||
// Write bytes to the scanner. Will copy the bytes to stdout
|
||||
func (s *stdoutScanner) Write(data []byte) (n int, err error) {
|
||||
input := stripansi.Strip(string(data))
|
||||
if !s.versionDetected {
|
||||
v, err := detectViteVersion(input)
|
||||
if v != "" || err != nil {
|
||||
if err != nil {
|
||||
logutils.LogRed("ViteStdoutScanner: %s", err)
|
||||
v = "v0.0.0"
|
||||
}
|
||||
s.ViteServerVersionC <- v
|
||||
s.versionDetected = true
|
||||
}
|
||||
}
|
||||
|
||||
match := strings.Index(input, "Local:")
|
||||
if match != -1 {
|
||||
sc := bufio.NewScanner(strings.NewReader(input))
|
||||
@@ -47,3 +64,21 @@ func (s *stdoutScanner) Write(data []byte) (n int, err error) {
|
||||
}
|
||||
return os.Stdout.Write(data)
|
||||
}
|
||||
|
||||
func detectViteVersion(line string) (string, error) {
|
||||
s := strings.Split(strings.TrimSpace(line), " ")
|
||||
if strings.ToLower(s[0]) != "vite" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(line) < 2 {
|
||||
return "", fmt.Errorf("unable to parse vite version")
|
||||
}
|
||||
|
||||
v := s[1]
|
||||
if !semver.IsValid(v) {
|
||||
return "", fmt.Errorf("%s is not a valid vite version string", v)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^2.9.9"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
iofs "io/fs"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
|
||||
@@ -104,17 +106,35 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
||||
}
|
||||
|
||||
if frontendDevServerURL != "" {
|
||||
if devServer == "" {
|
||||
return nil, fmt.Errorf("Unable to use FrontendDevServerUrl without a DevServer address")
|
||||
if os.Getenv("legacyusedevsererinsteadofcustomscheme") != "" {
|
||||
startURL, err := url.Parse("http://" + devServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, "starturl", startURL)
|
||||
}
|
||||
|
||||
startURL, err := url.Parse("http://" + devServer)
|
||||
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
|
||||
|
||||
externalURL, err := url.Parse(frontendDevServerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, "starturl", startURL)
|
||||
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
|
||||
if externalURL.Host == "" {
|
||||
return nil, fmt.Errorf("Invalid frontend:dev:serverUrl missing protocol scheme?")
|
||||
}
|
||||
|
||||
waitCb := func() { myLogger.Debug("Waiting for frontend DevServer '%s' to be ready", externalURL) }
|
||||
if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) {
|
||||
myLogger.Error("Timeout waiting for frontend DevServer")
|
||||
}
|
||||
|
||||
handler := assetserver.NewExternalAssetsHandler(myLogger, assetConfig, externalURL)
|
||||
assetConfig.Assets = nil
|
||||
assetConfig.Handler = handler
|
||||
assetConfig.Middleware = nil
|
||||
|
||||
myLogger.Info("Serving assets from frontend DevServer URL: %s", frontendDevServerURL)
|
||||
} else {
|
||||
@@ -246,3 +266,22 @@ func tryInferAssetDirFromFS(assets iofs.FS) (string, error) {
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func checkPortIsOpen(host string, timeout time.Duration, waitCB func()) (ret bool) {
|
||||
if timeout == 0 {
|
||||
timeout = time.Minute
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
conn, _ := net.DialTimeout("tcp", host, 2*time.Second)
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
waitCB()
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ typedef int Role;
|
||||
|
||||
static const Role AppMenu = 1;
|
||||
static const Role EditMenu = 2;
|
||||
static const Role WindowMenu = 3;
|
||||
|
||||
#endif /* Role_h */
|
||||
|
||||
@@ -68,12 +68,20 @@
|
||||
appName = [[NSProcessInfo processInfo] processName];
|
||||
}
|
||||
WailsMenu *appMenu = [[[WailsMenu new] initWithNSTitle:appName] autorelease];
|
||||
|
||||
if (ctx.aboutTitle != nil) {
|
||||
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
[appMenu addItem:[self newMenuItem:[@"Hide " stringByAppendingString:appName] :@selector(hide:) :@"h" :NSEventModifierFlagCommand]];
|
||||
[appMenu addItem:[self newMenuItem:@"Hide Others" :@selector(hideOtherApplications:) :@"h" :(NSEventModifierFlagOption | NSEventModifierFlagCommand)]];
|
||||
[appMenu addItem:[self newMenuItem:@"Show All" :@selector(unhideAllApplications:) :@""]];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
id quitTitle = [@"Quit " stringByAppendingString:appName];
|
||||
NSMenuItem* quitMenuItem = [self newMenuItem:quitTitle :@selector(Quit) :@"q" :NSEventModifierFlagCommand];
|
||||
quitMenuItem.target = ctx;
|
||||
if (ctx.aboutTitle != nil) {
|
||||
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
|
||||
}
|
||||
[appMenu addItem:quitMenuItem];
|
||||
[self appendSubmenu:appMenu];
|
||||
break;
|
||||
@@ -100,6 +108,17 @@
|
||||
[editMenu appendSubmenu:speechMenu];
|
||||
[self appendSubmenu:editMenu];
|
||||
|
||||
break;
|
||||
}
|
||||
case WindowMenu:
|
||||
{
|
||||
WailsMenu *windowMenu = [[[WailsMenu new] initWithNSTitle:@"Window"] autorelease];
|
||||
[windowMenu addItem:[self newMenuItem:@"Minimize" :@selector(performMiniaturize:) :@"m" :NSEventModifierFlagCommand]];
|
||||
[windowMenu addItem:[self newMenuItem:@"Zoom" :@selector(performZoom:) :@""]];
|
||||
[windowMenu addItem:[NSMenuItem separatorItem]];
|
||||
[windowMenu addItem:[self newMenuItem:@"Full Screen" :@selector(enterFullScreenMode:) :@"f" :(NSEventModifierFlagControl | NSEventModifierFlagCommand)]];
|
||||
[self appendSubmenu:windowMenu];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"html/template"
|
||||
"log"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
@@ -114,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() {
|
||||
@@ -337,7 +335,11 @@ func (f *Frontend) processMessage(message string) {
|
||||
}
|
||||
|
||||
func (f *Frontend) Callback(message string) {
|
||||
f.ExecJS(`window.wails.Callback(` + strconv.Quote(message) + `);`)
|
||||
escaped, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
|
||||
}
|
||||
|
||||
func (f *Frontend) ExecJS(js string) {
|
||||
|
||||
@@ -81,7 +81,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unsafe"
|
||||
@@ -434,7 +433,11 @@ func (f *Frontend) processMessage(message string) {
|
||||
}
|
||||
|
||||
func (f *Frontend) Callback(message string) {
|
||||
f.ExecJS(`window.wails.Callback(` + strconv.Quote(message) + `);`)
|
||||
escaped, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`)
|
||||
}
|
||||
|
||||
func (f *Frontend) startDrag() {
|
||||
@@ -463,7 +466,6 @@ var requestBuffer = make(chan webview.Request, 100)
|
||||
func (f *Frontend) startRequestProcessor() {
|
||||
for request := range requestBuffer {
|
||||
f.assets.ServeWebViewRequest(request)
|
||||
request.Release()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
||||
"github.com/wailsapp/wails/v2/internal/go-common-file-dialog/cfd"
|
||||
"golang.org/x/sys/windows"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (f *Frontend) getHandleForDialog() w32.HWND {
|
||||
@@ -40,27 +41,20 @@ func (f *Frontend) OpenDirectoryDialog(options frontend.OpenDialogOptions) (stri
|
||||
Role: "PickFolder",
|
||||
Folder: defaultFolder,
|
||||
}
|
||||
thisDialog, err := cfd.NewSelectFolderDialog(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
thisDialog.SetParentWindowHandle(f.getHandleForDialog())
|
||||
defer func(thisDialog cfd.SelectFolderDialog) {
|
||||
err := thisDialog.Release()
|
||||
if err != nil {
|
||||
println("ERROR: Unable to release dialog:", err.Error())
|
||||
}
|
||||
}(thisDialog)
|
||||
result, err := thisDialog.ShowAndGetResult()
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewSelectFolderDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrorCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result, nil
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
// OpenFileDialog prompts the user to select a file
|
||||
func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, error) {
|
||||
|
||||
defaultFolder, err := getDefaultFolder(options.DefaultDirectory)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -72,22 +66,16 @@ func (f *Frontend) OpenFileDialog(options frontend.OpenDialogOptions) (string, e
|
||||
FileName: options.DefaultFilename,
|
||||
Title: options.Title,
|
||||
}
|
||||
thisdialog, err := cfd.NewOpenFileDialog(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
thisdialog.SetParentWindowHandle(f.getHandleForDialog())
|
||||
defer func(thisdialog cfd.OpenFileDialog) {
|
||||
err := thisdialog.Release()
|
||||
if err != nil {
|
||||
println("ERROR: Unable to release dialog:", err.Error())
|
||||
}
|
||||
}(thisdialog)
|
||||
result, err := thisdialog.ShowAndGetResult()
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewOpenFileDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrorCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result, nil
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
// OpenMultipleFilesDialog prompts the user to select a file
|
||||
@@ -105,22 +93,16 @@ func (f *Frontend) OpenMultipleFilesDialog(options frontend.OpenDialogOptions) (
|
||||
FileName: options.DefaultFilename,
|
||||
Folder: defaultFolder,
|
||||
}
|
||||
thisdialog, err := cfd.NewOpenMultipleFilesDialog(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
thisdialog.SetParentWindowHandle(f.getHandleForDialog())
|
||||
defer func(thisdialog cfd.OpenMultipleFilesDialog) {
|
||||
err := thisdialog.Release()
|
||||
if err != nil {
|
||||
println("ERROR: Unable to release dialog:", err.Error())
|
||||
}
|
||||
}(thisdialog)
|
||||
result, err := thisdialog.ShowAndGetResults()
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewOpenMultipleFilesDialog(config)
|
||||
}, true)
|
||||
|
||||
if err != nil && err != cfd.ErrorCancelled {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
return result.([]string), nil
|
||||
}
|
||||
|
||||
// SaveFileDialog prompts the user to select a file
|
||||
@@ -131,26 +113,44 @@ func (f *Frontend) SaveFileDialog(options frontend.SaveDialogOptions) (string, e
|
||||
return "", err
|
||||
}
|
||||
|
||||
saveDialog, err := cfd.NewSaveFileDialog(cfd.DialogConfig{
|
||||
config := cfd.DialogConfig{
|
||||
Title: options.Title,
|
||||
Role: "SaveFile",
|
||||
FileFilters: convertFilters(options.Filters),
|
||||
FileName: options.DefaultFilename,
|
||||
Folder: defaultFolder,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
saveDialog.SetParentWindowHandle(f.getHandleForDialog())
|
||||
err = saveDialog.Show()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result, err := saveDialog.GetResult()
|
||||
|
||||
result, err := f.showCfdDialog(
|
||||
func() (cfd.Dialog, error) {
|
||||
return cfd.NewSaveFileDialog(config)
|
||||
}, false)
|
||||
|
||||
if err != nil && err != cfd.ErrorCancelled {
|
||||
return "", err
|
||||
}
|
||||
return result, nil
|
||||
return result.(string), nil
|
||||
}
|
||||
|
||||
func (f *Frontend) showCfdDialog(newDlg func() (cfd.Dialog, error), isMultiSelect bool) (any, error) {
|
||||
return invokeSync(f.mainWindow, func() (any, error) {
|
||||
dlg, err := newDlg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err := dlg.Release()
|
||||
if err != nil {
|
||||
println("ERROR: Unable to release dialog:", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
dlg.SetParentWindowHandle(f.getHandleForDialog())
|
||||
if multi, _ := dlg.(cfd.OpenMultipleFilesDialog); multi != nil && isMultiSelect {
|
||||
return multi.ShowAndGetResults()
|
||||
}
|
||||
return dlg.ShowAndGetResult()
|
||||
})
|
||||
}
|
||||
|
||||
func calculateMessageDialogFlags(options frontend.MessageDialogOptions) uint32 {
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
@@ -637,8 +636,12 @@ func (f *Frontend) processMessage(message string) {
|
||||
}
|
||||
|
||||
func (f *Frontend) Callback(message string) {
|
||||
escaped, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.mainWindow.Invoke(func() {
|
||||
f.chromium.Eval(`window.wails.Callback(` + strconv.Quote(message) + `);`)
|
||||
f.chromium.Eval(`window.wails.Callback(` + string(escaped) + `);`)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge"
|
||||
@@ -323,3 +324,14 @@ func (w *Window) SetTheme(theme winoptions.Theme) {
|
||||
w.UpdateTheme()
|
||||
})
|
||||
}
|
||||
|
||||
func invokeSync[T any](cba *Window, fn func() (T, error)) (res T, err error) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
cba.Invoke(func() {
|
||||
res, err = fn()
|
||||
wg.Done()
|
||||
})
|
||||
wg.Wait()
|
||||
return res, err
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func (z *Zypper) Packages() packagemap {
|
||||
{Name: "gtk3-devel", SystemPackage: true, Library: true},
|
||||
},
|
||||
"libwebkit": []*Package{
|
||||
{Name: "webkit2gtk3-soup2-devel", SystemPackage: true, Library: true},
|
||||
{Name: "webkit2gtk3-devel", SystemPackage: true, Library: true},
|
||||
},
|
||||
"gcc": []*Package{
|
||||
@@ -40,6 +41,7 @@ func (z *Zypper) Packages() packagemap {
|
||||
},
|
||||
"pkg-config": []*Package{
|
||||
{Name: "pkg-config", SystemPackage: true},
|
||||
{Name: "pkgconf-pkg-config", SystemPackage: true},
|
||||
},
|
||||
"npm": []*Package{
|
||||
{Name: "npm10", SystemPackage: true},
|
||||
@@ -60,7 +62,9 @@ func (z *Zypper) PackageInstalled(pkg *Package) (bool, error) {
|
||||
if pkg.SystemPackage == false {
|
||||
return false, nil
|
||||
}
|
||||
stdout, _, err := shell.RunCommand(".", "zypper", "info", pkg.Name)
|
||||
var env []string
|
||||
env = shell.SetEnv(env, "LANGUAGE", "en_US.utf-8")
|
||||
stdout, _, err := shell.RunCommandWithEnv(env, ".", "zypper", "info", pkg.Name)
|
||||
if err != nil {
|
||||
_, ok := err.(*exec.ExitError)
|
||||
if ok {
|
||||
@@ -83,7 +87,9 @@ func (z *Zypper) PackageAvailable(pkg *Package) (bool, error) {
|
||||
if pkg.SystemPackage == false {
|
||||
return false, nil
|
||||
}
|
||||
stdout, _, err := shell.RunCommand(".", "zypper", "info", pkg.Name)
|
||||
var env []string
|
||||
env = shell.SetEnv(env, "LANGUAGE", "en_US.utf-8")
|
||||
stdout, _, err := shell.RunCommandWithEnv(env, ".", "zypper", "info", pkg.Name)
|
||||
// We add a space to ensure we get a full match, not partial match
|
||||
if err != nil {
|
||||
_, ok := err.(*exec.ExitError)
|
||||
|
||||
+16
-19
@@ -1,7 +1,7 @@
|
||||
//go:build dev
|
||||
// +build dev
|
||||
|
||||
package devserver
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -10,21 +10,12 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
)
|
||||
|
||||
func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, options assetserver.Options) http.Handler {
|
||||
handler := newExternalAssetsHandler(logger, url, options.Handler)
|
||||
func NewExternalAssetsHandler(logger Logger, options assetserver.Options, url *url.URL) http.Handler {
|
||||
baseHandler := options.Handler
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.Handler) http.Handler {
|
||||
errSkipProxy := fmt.Errorf("skip proxying")
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
@@ -37,7 +28,7 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
||||
}
|
||||
|
||||
proxy.ModifyResponse = func(res *http.Response) error {
|
||||
if handler == nil {
|
||||
if baseHandler == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -53,11 +44,11 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
||||
}
|
||||
|
||||
proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) {
|
||||
if handler != nil && errors.Is(err, errSkipProxy) {
|
||||
if baseHandler != nil && errors.Is(err, errSkipProxy) {
|
||||
if logger != nil {
|
||||
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using AssetHandler", r.URL)
|
||||
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL)
|
||||
}
|
||||
handler.ServeHTTP(rw, r)
|
||||
baseHandler.ServeHTTP(rw, r)
|
||||
} else {
|
||||
if logger != nil {
|
||||
logger.Error("[ExternalAssetHandler] Proxy error: %v", err)
|
||||
@@ -66,18 +57,24 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
||||
}
|
||||
}
|
||||
|
||||
return http.HandlerFunc(
|
||||
var result http.Handler = http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method == http.MethodGet {
|
||||
proxy.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if handler != nil {
|
||||
handler.ServeHTTP(rw, req)
|
||||
if baseHandler != nil {
|
||||
baseHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
||||
})
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
result = middleware(result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -56,13 +56,7 @@ func (r legacyRequest) Response() webview.ResponseWriter {
|
||||
return &legacyRequestNoOpCloserResponseWriter{r.rw}
|
||||
}
|
||||
|
||||
func (r legacyRequest) AddRef() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r legacyRequest) Release() error {
|
||||
return nil
|
||||
}
|
||||
func (r legacyRequest) Close() error { return nil }
|
||||
|
||||
func (r *legacyRequest) request() (*http.Request, error) {
|
||||
if r.req != nil {
|
||||
@@ -81,6 +75,4 @@ type legacyRequestNoOpCloserResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (*legacyRequestNoOpCloserResponseWriter) Finish() error {
|
||||
return nil
|
||||
}
|
||||
func (*legacyRequestNoOpCloserResponseWriter) Finish() {}
|
||||
|
||||
@@ -22,6 +22,7 @@ type assetServerWebView struct {
|
||||
|
||||
// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server.
|
||||
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
||||
// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way.
|
||||
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
d.dispatchInit.Do(func() {
|
||||
workers := d.dispatchWorkers
|
||||
@@ -33,8 +34,11 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
for req := range workerC {
|
||||
uri, _ := req.URL()
|
||||
d.processWebViewRequest(req)
|
||||
req.Release()
|
||||
if err := req.Close(); err != nil {
|
||||
d.logError("Unable to call close for request for uri '%s'", uri)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -45,12 +49,6 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
d.dispatchReqC = dispatchC
|
||||
})
|
||||
|
||||
if err := req.AddRef(); err != nil {
|
||||
uri, _ := req.URL()
|
||||
d.logError("Unable to call AddRef for request '%s'", uri)
|
||||
return
|
||||
}
|
||||
|
||||
d.dispatchReqC <- req
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,5 @@ type Request interface {
|
||||
|
||||
Response() ResponseWriter
|
||||
|
||||
AddRef() error
|
||||
Release() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -118,11 +118,9 @@ import (
|
||||
)
|
||||
|
||||
// NewRequest creates as new WebViewRequest based on a pointer to an `id<WKURLSchemeTask>`
|
||||
//
|
||||
// Please make sure to call Release() when finished using the request.
|
||||
func NewRequest(wkURLSchemeTask unsafe.Pointer) Request {
|
||||
C.URLSchemeTaskRetain(wkURLSchemeTask)
|
||||
return &request{task: wkURLSchemeTask}
|
||||
return newRequestFinalizer(&request{task: wkURLSchemeTask})
|
||||
}
|
||||
|
||||
var _ Request = &request{}
|
||||
@@ -135,16 +133,6 @@ type request struct {
|
||||
rw *responseWriter
|
||||
}
|
||||
|
||||
func (r *request) AddRef() error {
|
||||
C.URLSchemeTaskRetain(r.task)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) Release() error {
|
||||
C.URLSchemeTaskRelease(r.task)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) URL() (string, error) {
|
||||
return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil
|
||||
}
|
||||
@@ -205,6 +193,16 @@ func (r *request) Response() ResponseWriter {
|
||||
return r.rw
|
||||
}
|
||||
|
||||
func (r *request) Close() error {
|
||||
var err error
|
||||
if r.body != nil {
|
||||
err = r.body.Close()
|
||||
}
|
||||
r.Response().Finish()
|
||||
C.URLSchemeTaskRelease(r.task)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = &requestBodyStreamReader{}
|
||||
|
||||
type requestBodyStreamReader struct {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user