mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 10:41:14 +08:00

* [assetserver, darwin] Fix copying request headers by using strdup * [assetserver, linux] Fake some basic http headers for legacy webkit2 versions to support proxying requests to other servers This fixes the devserver on v2 for newer vite versions that use the custom scheme. * [v2, windows] 304 responses are going to hang the WebView2 so prevent them by removing cache related headers in the request. * [v2, dev] Now uses the custom schemes `wails://` on macOS and Linux for all Vite versions. Prevent missing reload after fast multiple savings on Linux and Windows.
899 lines
23 KiB
Go
899 lines
23 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package windows
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/bep/debounce"
|
|
"github.com/wailsapp/wails/v2/internal/binding"
|
|
"github.com/wailsapp/wails/v2/internal/frontend"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/go-webview2/pkg/edge"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/win32"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
|
wailsruntime "github.com/wailsapp/wails/v2/internal/frontend/runtime"
|
|
"github.com/wailsapp/wails/v2/internal/logger"
|
|
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
|
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
|
"github.com/wailsapp/wails/v2/pkg/options"
|
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
|
)
|
|
|
|
const startURL = "http://wails.localhost/"
|
|
|
|
type Screen = frontend.Screen
|
|
|
|
type Frontend struct {
|
|
|
|
// Context
|
|
ctx context.Context
|
|
|
|
frontendOptions *options.App
|
|
logger *logger.Logger
|
|
chromium *edge.Chromium
|
|
debug bool
|
|
|
|
// Assets
|
|
assets *assetserver.AssetServer
|
|
startURL *url.URL
|
|
|
|
// main window handle
|
|
mainWindow *Window
|
|
bindings *binding.Bindings
|
|
dispatcher frontend.Dispatcher
|
|
|
|
hasStarted bool
|
|
|
|
// Windows build number
|
|
versionInfo *operatingsystem.WindowsVersionInfo
|
|
resizeDebouncer func(f func())
|
|
}
|
|
|
|
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
|
|
|
// Get Windows build number
|
|
versionInfo, _ := operatingsystem.GetWindowsVersionInfo()
|
|
|
|
result := &Frontend{
|
|
frontendOptions: appoptions,
|
|
logger: myLogger,
|
|
bindings: appBindings,
|
|
dispatcher: dispatcher,
|
|
ctx: ctx,
|
|
versionInfo: versionInfo,
|
|
}
|
|
|
|
if appoptions.Windows != nil {
|
|
if appoptions.Windows.ResizeDebounceMS > 0 {
|
|
result.resizeDebouncer = debounce.New(time.Duration(appoptions.Windows.ResizeDebounceMS) * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// We currently can't use wails://wails/ as other platforms do, therefore we map the assets sever onto the following url.
|
|
result.startURL, _ = url.Parse(startURL)
|
|
|
|
if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
|
|
result.startURL = _starturl
|
|
return result
|
|
}
|
|
|
|
if port, _ := ctx.Value("assetserverport").(string); port != "" {
|
|
result.startURL.Host = net.JoinHostPort(result.startURL.Host, port)
|
|
}
|
|
|
|
var bindings string
|
|
var err error
|
|
if _obfuscated, _ := ctx.Value("obfuscated").(bool); !_obfuscated {
|
|
bindings, err = appBindings.ToJSON()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
appBindings.DB().UpdateObfuscatedCallMap()
|
|
}
|
|
|
|
assets, err := assetserver.NewAssetServerMainPage(bindings, appoptions, ctx.Value("assetdir") != nil, myLogger, wailsruntime.RuntimeAssetsBundle)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
result.assets = assets
|
|
|
|
return result
|
|
}
|
|
|
|
func (f *Frontend) WindowReload() {
|
|
f.ExecJS("runtime.WindowReload();")
|
|
}
|
|
|
|
func (f *Frontend) WindowSetSystemDefaultTheme() {
|
|
f.mainWindow.SetTheme(windows.SystemDefault)
|
|
}
|
|
|
|
func (f *Frontend) WindowSetLightTheme() {
|
|
f.mainWindow.SetTheme(windows.Light)
|
|
}
|
|
|
|
func (f *Frontend) WindowSetDarkTheme() {
|
|
f.mainWindow.SetTheme(windows.Dark)
|
|
}
|
|
|
|
func (f *Frontend) Run(ctx context.Context) error {
|
|
f.ctx = ctx
|
|
|
|
f.chromium = edge.NewChromium()
|
|
|
|
mainWindow := NewWindow(nil, f.frontendOptions, f.versionInfo, f.chromium)
|
|
f.mainWindow = mainWindow
|
|
|
|
var _debug = ctx.Value("debug")
|
|
if _debug != nil {
|
|
f.debug = _debug.(bool)
|
|
}
|
|
|
|
f.WindowCenter()
|
|
f.setupChromium()
|
|
|
|
mainWindow.OnSize().Bind(func(arg *winc.Event) {
|
|
if f.frontendOptions.Frameless {
|
|
// If the window is frameless and we are minimizing, then we need to suppress the Resize on the
|
|
// WebView2. If we don't do this, restoring does not work as expected and first restores with some wrong
|
|
// size during the restore animation and only fully renders when the animation is done. This highly
|
|
// depends on the content in the WebView, see https://github.com/wailsapp/wails/issues/1319
|
|
event, _ := arg.Data.(*winc.SizeEventData)
|
|
if event != nil && event.Type == w32.SIZE_MINIMIZED {
|
|
return
|
|
}
|
|
}
|
|
|
|
if f.resizeDebouncer != nil {
|
|
f.resizeDebouncer(func() {
|
|
f.mainWindow.Invoke(func() {
|
|
f.chromium.Resize()
|
|
})
|
|
})
|
|
} else {
|
|
f.chromium.Resize()
|
|
}
|
|
})
|
|
|
|
mainWindow.OnClose().Bind(func(arg *winc.Event) {
|
|
if f.frontendOptions.HideWindowOnClose {
|
|
f.WindowHide()
|
|
} else {
|
|
f.Quit()
|
|
}
|
|
})
|
|
|
|
go func() {
|
|
if f.frontendOptions.OnStartup != nil {
|
|
f.frontendOptions.OnStartup(f.ctx)
|
|
}
|
|
}()
|
|
mainWindow.UpdateTheme()
|
|
return nil
|
|
}
|
|
|
|
func (f *Frontend) WindowClose() {
|
|
if f.mainWindow != nil {
|
|
f.mainWindow.Close()
|
|
}
|
|
}
|
|
|
|
func (f *Frontend) RunMainLoop() {
|
|
_ = winc.RunMainLoop()
|
|
}
|
|
|
|
func (f *Frontend) WindowCenter() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
f.mainWindow.Center()
|
|
}
|
|
|
|
func (f *Frontend) WindowSetAlwaysOnTop(b bool) {
|
|
runtime.LockOSThread()
|
|
f.mainWindow.SetAlwaysOnTop(b)
|
|
}
|
|
|
|
func (f *Frontend) WindowSetPosition(x, y int) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
f.mainWindow.SetPos(x, y)
|
|
}
|
|
func (f *Frontend) WindowGetPosition() (int, int) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
return f.mainWindow.Pos()
|
|
}
|
|
|
|
func (f *Frontend) WindowSetSize(width, height int) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
f.mainWindow.SetSize(width, height)
|
|
}
|
|
|
|
func (f *Frontend) WindowGetSize() (int, int) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
return f.mainWindow.Size()
|
|
}
|
|
|
|
func (f *Frontend) WindowSetTitle(title string) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
f.mainWindow.SetText(title)
|
|
}
|
|
|
|
func (f *Frontend) WindowFullscreen() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
|
f.ExecJS("window.wails.flags.enableResize = false;")
|
|
}
|
|
f.mainWindow.Fullscreen()
|
|
}
|
|
|
|
func (f *Frontend) WindowReloadApp() {
|
|
f.ExecJS(fmt.Sprintf("window.location.href = '%s';", f.startURL))
|
|
}
|
|
|
|
func (f *Frontend) WindowUnfullscreen() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
|
f.ExecJS("window.wails.flags.enableResize = true;")
|
|
}
|
|
f.mainWindow.UnFullscreen()
|
|
}
|
|
|
|
func (f *Frontend) WindowShow() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
f.ShowWindow()
|
|
}
|
|
|
|
func (f *Frontend) WindowHide() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
f.mainWindow.Hide()
|
|
}
|
|
|
|
func (f *Frontend) WindowMaximise() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
if f.hasStarted {
|
|
if !f.frontendOptions.DisableResize {
|
|
f.mainWindow.Maximise()
|
|
}
|
|
} else {
|
|
f.frontendOptions.WindowStartState = options.Maximised
|
|
}
|
|
}
|
|
|
|
func (f *Frontend) WindowToggleMaximise() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
if !f.hasStarted {
|
|
return
|
|
}
|
|
if f.mainWindow.IsMaximised() {
|
|
f.WindowUnmaximise()
|
|
} else {
|
|
f.WindowMaximise()
|
|
}
|
|
}
|
|
|
|
func (f *Frontend) WindowUnmaximise() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
if f.mainWindow.Form.IsFullScreen() {
|
|
return
|
|
}
|
|
f.mainWindow.Restore()
|
|
}
|
|
|
|
func (f *Frontend) WindowMinimise() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
if f.hasStarted {
|
|
f.mainWindow.Minimise()
|
|
} else {
|
|
f.frontendOptions.WindowStartState = options.Minimised
|
|
}
|
|
}
|
|
|
|
func (f *Frontend) WindowUnminimise() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
if f.mainWindow.Form.IsFullScreen() {
|
|
return
|
|
}
|
|
f.mainWindow.Restore()
|
|
}
|
|
|
|
func (f *Frontend) WindowSetMinSize(width int, height int) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
f.mainWindow.SetMinSize(width, height)
|
|
}
|
|
func (f *Frontend) WindowSetMaxSize(width int, height int) {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
f.mainWindow.SetMaxSize(width, height)
|
|
}
|
|
|
|
func (f *Frontend) WindowSetBackgroundColour(col *options.RGBA) {
|
|
if col == nil {
|
|
return
|
|
}
|
|
|
|
f.mainWindow.Invoke(func() {
|
|
win32.SetBackgroundColour(f.mainWindow.Handle(), col.R, col.G, col.B)
|
|
|
|
controller := f.chromium.GetController()
|
|
controller2 := controller.GetICoreWebView2Controller2()
|
|
|
|
backgroundCol := edge.COREWEBVIEW2_COLOR{
|
|
A: col.A,
|
|
R: col.R,
|
|
G: col.G,
|
|
B: col.B,
|
|
}
|
|
|
|
// WebView2 only has 0 and 255 as valid values.
|
|
if backgroundCol.A > 0 && backgroundCol.A < 255 {
|
|
backgroundCol.A = 255
|
|
}
|
|
|
|
if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.WebviewIsTransparent {
|
|
backgroundCol.A = 0
|
|
}
|
|
|
|
err := controller2.PutDefaultBackgroundColor(backgroundCol)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
func (f *Frontend) ScreenGetAll() ([]Screen, error) {
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
screens := []Screen{}
|
|
err := error(nil)
|
|
f.mainWindow.Invoke(func() {
|
|
screens, err = GetAllScreens(f.mainWindow.Handle())
|
|
wg.Done()
|
|
|
|
})
|
|
wg.Wait()
|
|
return screens, err
|
|
}
|
|
|
|
func (f *Frontend) Show() {
|
|
f.mainWindow.Show()
|
|
}
|
|
|
|
func (f *Frontend) Hide() {
|
|
f.mainWindow.Hide()
|
|
}
|
|
|
|
func (f *Frontend) WindowIsMaximised() bool {
|
|
return f.mainWindow.IsMaximised()
|
|
}
|
|
|
|
func (f *Frontend) WindowIsMinimised() bool {
|
|
return f.mainWindow.IsMinimised()
|
|
}
|
|
|
|
func (f *Frontend) WindowIsNormal() bool {
|
|
return f.mainWindow.IsNormal()
|
|
}
|
|
|
|
func (f *Frontend) WindowIsFullscreen() bool {
|
|
return f.mainWindow.IsFullScreen()
|
|
}
|
|
|
|
func (f *Frontend) Quit() {
|
|
if f.frontendOptions.OnBeforeClose != nil && f.frontendOptions.OnBeforeClose(f.ctx) {
|
|
return
|
|
}
|
|
// Exit must be called on the Main-Thread. It calls PostQuitMessage which sends the WM_QUIT message to the thread's
|
|
// message queue and our message queue runs on the Main-Thread.
|
|
f.mainWindow.Invoke(winc.Exit)
|
|
}
|
|
|
|
func (f *Frontend) setupChromium() {
|
|
chromium := f.chromium
|
|
|
|
disableFeatues := []string{}
|
|
if !f.frontendOptions.EnableFraudulentWebsiteDetection {
|
|
disableFeatues = append(disableFeatues, "msSmartScreenProtection")
|
|
}
|
|
|
|
if opts := f.frontendOptions.Windows; opts != nil {
|
|
chromium.DataPath = opts.WebviewUserDataPath
|
|
chromium.BrowserPath = opts.WebviewBrowserPath
|
|
|
|
if opts.WebviewGpuIsDisabled {
|
|
chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, "--disable-gpu")
|
|
}
|
|
if opts.WebviewDisableRendererCodeIntegrity {
|
|
disableFeatues = append(disableFeatues, "RendererCodeIntegrity")
|
|
}
|
|
}
|
|
|
|
if len(disableFeatues) > 0 {
|
|
arg := fmt.Sprintf("--disable-features=%s", strings.Join(disableFeatues, ","))
|
|
chromium.AdditionalBrowserArgs = append(chromium.AdditionalBrowserArgs, arg)
|
|
}
|
|
|
|
chromium.MessageCallback = f.processMessage
|
|
chromium.WebResourceRequestedCallback = f.processRequest
|
|
chromium.NavigationCompletedCallback = f.navigationCompleted
|
|
chromium.AcceleratorKeyCallback = func(vkey uint) bool {
|
|
w32.PostMessage(f.mainWindow.Handle(), w32.WM_KEYDOWN, uintptr(vkey), 0)
|
|
return false
|
|
}
|
|
chromium.ProcessFailedCallback = func(sender *edge.ICoreWebView2, args *edge.ICoreWebView2ProcessFailedEventArgs) {
|
|
kind, err := args.GetProcessFailedKind()
|
|
if err != nil {
|
|
f.logger.Error("GetProcessFailedKind: %s", err)
|
|
return
|
|
}
|
|
|
|
f.logger.Error("WebVie2wProcess failed with kind %d", kind)
|
|
switch kind {
|
|
case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_BROWSER_PROCESS_EXITED:
|
|
// => The app has to recreate a new WebView to recover from this failure.
|
|
messages := windows.DefaultMessages()
|
|
if f.frontendOptions.Windows != nil && f.frontendOptions.Windows.Messages != nil {
|
|
messages = f.frontendOptions.Windows.Messages
|
|
}
|
|
winc.Errorf(f.mainWindow, messages.WebView2ProcessCrash)
|
|
os.Exit(-1)
|
|
case edge.COREWEBVIEW2_PROCESS_FAILED_KIND_RENDER_PROCESS_EXITED,
|
|
edge.COREWEBVIEW2_PROCESS_FAILED_KIND_FRAME_RENDER_PROCESS_EXITED:
|
|
// => A new render process is created automatically and navigated to an error page.
|
|
// => Make sure that the error page is shown.
|
|
if !f.hasStarted {
|
|
// NavgiationCompleted didn't come in, make sure the chromium is shown
|
|
chromium.Show()
|
|
}
|
|
if !f.mainWindow.hasBeenShown {
|
|
// The window has never been shown, make sure to show it
|
|
f.ShowWindow()
|
|
}
|
|
}
|
|
}
|
|
|
|
chromium.Embed(f.mainWindow.Handle())
|
|
chromium.Resize()
|
|
settings, err := chromium.GetSettings()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = settings.PutAreDefaultContextMenusEnabled(f.debug)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = settings.PutAreDevToolsEnabled(f.debug)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if opts := f.frontendOptions.Windows; opts != nil {
|
|
if opts.ZoomFactor > 0.0 {
|
|
chromium.PutZoomFactor(opts.ZoomFactor)
|
|
}
|
|
err = settings.PutIsZoomControlEnabled(opts.IsZoomControlEnabled)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
err = settings.PutIsStatusBarEnabled(false)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = settings.PutAreBrowserAcceleratorKeysEnabled(false)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = settings.PutIsSwipeNavigationEnabled(false)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if f.debug && f.frontendOptions.Debug.OpenInspectorOnStartup {
|
|
chromium.OpenDevToolsWindow()
|
|
}
|
|
|
|
// Setup focus event handler
|
|
onFocus := f.mainWindow.OnSetFocus()
|
|
onFocus.Bind(f.onFocus)
|
|
|
|
// Set background colour
|
|
f.WindowSetBackgroundColour(f.frontendOptions.BackgroundColour)
|
|
|
|
chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow)
|
|
chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)
|
|
chromium.Navigate(f.startURL.String())
|
|
}
|
|
|
|
type EventNotify struct {
|
|
Name string `json:"name"`
|
|
Data []interface{} `json:"data"`
|
|
}
|
|
|
|
func (f *Frontend) Notify(name string, data ...interface{}) {
|
|
notification := EventNotify{
|
|
Name: name,
|
|
Data: data,
|
|
}
|
|
payload, err := json.Marshal(notification)
|
|
if err != nil {
|
|
f.logger.Error(err.Error())
|
|
return
|
|
}
|
|
f.ExecJS(`window.wails.EventsNotify('` + template.JSEscapeString(string(payload)) + `');`)
|
|
}
|
|
|
|
func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) {
|
|
// Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but
|
|
// we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request.
|
|
if reqHeaders, err := req.GetHeaders(); err == nil {
|
|
useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent)
|
|
useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ")
|
|
reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent)
|
|
reqHeaders.Release()
|
|
}
|
|
|
|
if f.assets == nil {
|
|
// We are using the devServer let the WebView2 handle the request with its default handler
|
|
return
|
|
}
|
|
|
|
//Get the request
|
|
uri, _ := req.GetUri()
|
|
reqUri, err := url.ParseRequestURI(uri)
|
|
if err != nil {
|
|
f.logger.Error("Unable to parse equest uri %s: %s", uri, err)
|
|
return
|
|
}
|
|
|
|
if reqUri.Scheme != f.startURL.Scheme {
|
|
// Let the WebView2 handle the request with its default handler
|
|
return
|
|
} else if reqUri.Host != f.startURL.Host {
|
|
// Let the WebView2 handle the request with its default handler
|
|
return
|
|
}
|
|
|
|
rw := httptest.NewRecorder()
|
|
f.assets.ProcessHTTPRequestLegacy(rw, coreWebview2RequestToHttpRequest(req))
|
|
|
|
headers := []string{}
|
|
for k, v := range rw.Header() {
|
|
headers = append(headers, fmt.Sprintf("%s: %s", k, strings.Join(v, ",")))
|
|
}
|
|
|
|
code := rw.Code
|
|
if code == http.StatusNotModified {
|
|
// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
|
|
// requests including IPC calls.
|
|
f.logger.Error("%s: AssetServer returned 304 - StatusNotModified which are going to hang WebView2, changed code to 505 - StatusInternalServerError", uri)
|
|
code = http.StatusInternalServerError
|
|
}
|
|
|
|
env := f.chromium.Environment()
|
|
response, err := env.CreateWebResourceResponse(rw.Body.Bytes(), code, http.StatusText(code), strings.Join(headers, "\n"))
|
|
if err != nil {
|
|
f.logger.Error("CreateWebResourceResponse Error: %s", err)
|
|
return
|
|
}
|
|
defer response.Release()
|
|
|
|
// Send response back
|
|
err = args.PutResponse(response)
|
|
if err != nil {
|
|
f.logger.Error("PutResponse Error: %s", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
var edgeMap = map[string]uintptr{
|
|
"n-resize": w32.HTTOP,
|
|
"ne-resize": w32.HTTOPRIGHT,
|
|
"e-resize": w32.HTRIGHT,
|
|
"se-resize": w32.HTBOTTOMRIGHT,
|
|
"s-resize": w32.HTBOTTOM,
|
|
"sw-resize": w32.HTBOTTOMLEFT,
|
|
"w-resize": w32.HTLEFT,
|
|
"nw-resize": w32.HTTOPLEFT,
|
|
}
|
|
|
|
func (f *Frontend) processMessage(message string) {
|
|
if message == "drag" {
|
|
if !f.mainWindow.IsFullScreen() {
|
|
err := f.startDrag()
|
|
if err != nil {
|
|
f.logger.Error(err.Error())
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
if message == "runtime:ready" {
|
|
cmd := fmt.Sprintf("window.wails.setCSSDragProperties('%s', '%s');", f.frontendOptions.CSSDragProperty, f.frontendOptions.CSSDragValue)
|
|
f.ExecJS(cmd)
|
|
return
|
|
}
|
|
|
|
if strings.HasPrefix(message, "resize:") {
|
|
if !f.mainWindow.IsFullScreen() {
|
|
sl := strings.Split(message, ":")
|
|
if len(sl) != 2 {
|
|
f.logger.Info("Unknown message returned from dispatcher: %+v", message)
|
|
return
|
|
}
|
|
edge := edgeMap[sl[1]]
|
|
err := f.startResize(edge)
|
|
if err != nil {
|
|
f.logger.Error(err.Error())
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
result, err := f.dispatcher.ProcessMessage(message, f)
|
|
if err != nil {
|
|
f.logger.Error(err.Error())
|
|
f.Callback(result)
|
|
return
|
|
}
|
|
if result == "" {
|
|
return
|
|
}
|
|
|
|
switch result[0] {
|
|
case 'c':
|
|
// Callback from a method call
|
|
f.Callback(result[1:])
|
|
default:
|
|
f.logger.Info("Unknown message returned from dispatcher: %+v", result)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (f *Frontend) Callback(message string) {
|
|
escaped, err := json.Marshal(message)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
f.mainWindow.Invoke(func() {
|
|
f.chromium.Eval(`window.wails.Callback(` + string(escaped) + `);`)
|
|
})
|
|
}
|
|
|
|
func (f *Frontend) startDrag() error {
|
|
if !w32.ReleaseCapture() {
|
|
return fmt.Errorf("unable to release mouse capture")
|
|
}
|
|
// Use PostMessage because we don't want to block the caller until dragging has been finished.
|
|
w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, w32.HTCAPTION, 0)
|
|
return nil
|
|
}
|
|
|
|
func (f *Frontend) startResize(border uintptr) error {
|
|
if !w32.ReleaseCapture() {
|
|
return fmt.Errorf("unable to release mouse capture")
|
|
}
|
|
// Use PostMessage because we don't want to block the caller until resizing has been finished.
|
|
w32.PostMessage(f.mainWindow.Handle(), w32.WM_NCLBUTTONDOWN, border, 0)
|
|
return nil
|
|
}
|
|
|
|
func (f *Frontend) ExecJS(js string) {
|
|
f.mainWindow.Invoke(func() {
|
|
f.chromium.Eval(js)
|
|
})
|
|
}
|
|
|
|
func (f *Frontend) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) {
|
|
if f.frontendOptions.OnDomReady != nil {
|
|
go f.frontendOptions.OnDomReady(f.ctx)
|
|
}
|
|
|
|
if f.frontendOptions.Frameless && f.frontendOptions.DisableResize == false {
|
|
f.ExecJS("window.wails.flags.enableResize = true;")
|
|
}
|
|
|
|
if f.hasStarted {
|
|
return
|
|
}
|
|
f.hasStarted = true
|
|
|
|
// Hack to make it visible: https://github.com/MicrosoftEdge/WebView2Feedback/issues/1077#issuecomment-825375026
|
|
err := f.chromium.Hide()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = f.chromium.Show()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if f.frontendOptions.StartHidden {
|
|
return
|
|
}
|
|
|
|
switch f.frontendOptions.WindowStartState {
|
|
case options.Maximised:
|
|
if !f.frontendOptions.DisableResize {
|
|
win32.ShowWindowMaximised(f.mainWindow.Handle())
|
|
} else {
|
|
win32.ShowWindow(f.mainWindow.Handle())
|
|
}
|
|
case options.Minimised:
|
|
win32.ShowWindowMinimised(f.mainWindow.Handle())
|
|
case options.Fullscreen:
|
|
f.mainWindow.Fullscreen()
|
|
win32.ShowWindow(f.mainWindow.Handle())
|
|
default:
|
|
if f.frontendOptions.Fullscreen {
|
|
f.mainWindow.Fullscreen()
|
|
}
|
|
win32.ShowWindow(f.mainWindow.Handle())
|
|
}
|
|
|
|
f.mainWindow.hasBeenShown = true
|
|
|
|
}
|
|
|
|
func (f *Frontend) ShowWindow() {
|
|
f.mainWindow.Invoke(func() {
|
|
if !f.mainWindow.hasBeenShown {
|
|
f.mainWindow.hasBeenShown = true
|
|
switch f.frontendOptions.WindowStartState {
|
|
case options.Maximised:
|
|
if !f.frontendOptions.DisableResize {
|
|
win32.ShowWindowMaximised(f.mainWindow.Handle())
|
|
} else {
|
|
win32.ShowWindow(f.mainWindow.Handle())
|
|
}
|
|
case options.Minimised:
|
|
win32.RestoreWindow(f.mainWindow.Handle())
|
|
case options.Fullscreen:
|
|
f.mainWindow.Fullscreen()
|
|
win32.ShowWindow(f.mainWindow.Handle())
|
|
default:
|
|
if f.frontendOptions.Fullscreen {
|
|
f.mainWindow.Fullscreen()
|
|
}
|
|
win32.ShowWindow(f.mainWindow.Handle())
|
|
}
|
|
} else {
|
|
if win32.IsWindowMinimised(f.mainWindow.Handle()) {
|
|
win32.RestoreWindow(f.mainWindow.Handle())
|
|
} else {
|
|
win32.ShowWindow(f.mainWindow.Handle())
|
|
}
|
|
}
|
|
w32.SetForegroundWindow(f.mainWindow.Handle())
|
|
w32.SetFocus(f.mainWindow.Handle())
|
|
})
|
|
|
|
}
|
|
|
|
func (f *Frontend) onFocus(arg *winc.Event) {
|
|
f.chromium.Focus()
|
|
}
|
|
|
|
func coreWebview2RequestToHttpRequest(coreReq *edge.ICoreWebView2WebResourceRequest) func() (*http.Request, error) {
|
|
return func() (r *http.Request, err error) {
|
|
header := http.Header{}
|
|
headers, err := coreReq.GetHeaders()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetHeaders Error: %s", err)
|
|
}
|
|
defer headers.Release()
|
|
|
|
headersIt, err := headers.GetIterator()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetIterator Error: %s", err)
|
|
}
|
|
defer headersIt.Release()
|
|
|
|
for {
|
|
has, err := headersIt.HasCurrentHeader()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("HasCurrentHeader Error: %s", err)
|
|
}
|
|
if !has {
|
|
break
|
|
}
|
|
|
|
name, value, err := headersIt.GetCurrentHeader()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetCurrentHeader Error: %s", err)
|
|
}
|
|
|
|
header.Set(name, value)
|
|
if _, err := headersIt.MoveNext(); err != nil {
|
|
return nil, fmt.Errorf("MoveNext Error: %s", err)
|
|
}
|
|
}
|
|
|
|
// WebView2 has problems when a request returns a 304 status code and the WebView2 is going to hang for other
|
|
// requests including IPC calls.
|
|
// So prevent 304 status codes by removing the headers that are used in combinationwith caching.
|
|
header.Del("If-Modified-Since")
|
|
header.Del("If-None-Match")
|
|
|
|
method, err := coreReq.GetMethod()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetMethod Error: %s", err)
|
|
}
|
|
|
|
uri, err := coreReq.GetUri()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GetUri Error: %s", err)
|
|
}
|
|
|
|
var body io.ReadCloser
|
|
if content, err := coreReq.GetContent(); err != nil {
|
|
return nil, fmt.Errorf("GetContent Error: %s", err)
|
|
} else if content != nil {
|
|
body = &iStreamReleaseCloser{stream: content}
|
|
}
|
|
|
|
req, err := http.NewRequest(method, uri, body)
|
|
if err != nil {
|
|
if body != nil {
|
|
body.Close()
|
|
}
|
|
return nil, err
|
|
}
|
|
req.Header = header
|
|
return req, nil
|
|
}
|
|
}
|
|
|
|
type iStreamReleaseCloser struct {
|
|
stream *edge.IStream
|
|
closed bool
|
|
}
|
|
|
|
func (i *iStreamReleaseCloser) Read(p []byte) (int, error) {
|
|
if i.closed {
|
|
return 0, io.ErrClosedPipe
|
|
}
|
|
return i.stream.Read(p)
|
|
}
|
|
|
|
func (i *iStreamReleaseCloser) Close() error {
|
|
if i.closed {
|
|
return nil
|
|
}
|
|
i.closed = true
|
|
return i.stream.Release()
|
|
}
|