mirror of
https://github.com/taigrr/wails.git
synced 2026-04-03 21:52:45 -07:00
Compare commits
14 Commits
v2.0.0-bet
...
linux-dial
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b296fbbb99 | ||
|
|
26c4f112e3 | ||
|
|
f338dff171 | ||
|
|
3c6ed12637 | ||
|
|
e2f3a11a33 | ||
|
|
0571deb290 | ||
|
|
451b357e40 | ||
|
|
84b67a8f53 | ||
|
|
448cf731bb | ||
|
|
9cb480f0f0 | ||
|
|
6825a631f5 | ||
|
|
39f91a030f | ||
|
|
202e4d5be8 | ||
|
|
045e58778a |
10
cmd/fs.go
10
cmd/fs.go
@@ -148,16 +148,6 @@ func (fs *FSHelper) LocalDir(dir string) (*Dir, error) {
|
||||
}, err
|
||||
}
|
||||
|
||||
// LoadRelativeFile loads the given file relative to the caller's directory
|
||||
func (fs *FSHelper) LoadRelativeFile(relativePath string) ([]byte, error) {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
fullPath, err := filepath.Abs(filepath.Join(path.Dir(filename), relativePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.ReadFile(fullPath)
|
||||
}
|
||||
|
||||
// GetSubdirs will return a list of FQPs to subdirectories in the given directory
|
||||
func (d *Dir) GetSubdirs() (map[string]string, error) {
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"log"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed linuxdb.yaml
|
||||
var LinuxDBYaml []byte
|
||||
|
||||
// LinuxDB is the database for linux distribution data.
|
||||
type LinuxDB struct {
|
||||
Distributions map[string]*Distribution `yaml:"distributions"`
|
||||
@@ -78,14 +82,10 @@ func (l *LinuxDB) GetDistro(distro string) *Distribution {
|
||||
// NewLinuxDB creates a new LinuxDB instance from the bundled
|
||||
// linuxdb.yaml file.
|
||||
func NewLinuxDB() *LinuxDB {
|
||||
data, err := fs.LoadRelativeFile("./linuxdb.yaml")
|
||||
if err != nil {
|
||||
log.Fatal("Could not load linuxdb.yaml")
|
||||
}
|
||||
result := LinuxDB{
|
||||
Distributions: make(map[string]*Distribution),
|
||||
}
|
||||
err = result.ImportData(data)
|
||||
err := result.ImportData(LinuxDBYaml)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -79,6 +80,12 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
forceBuild := false
|
||||
command.BoolFlag("f", "Force build application", &forceBuild)
|
||||
|
||||
updateGoMod := false
|
||||
command.BoolFlag("u", "Updates go.mod to use the same Wails version as the CLI", &updateGoMod)
|
||||
|
||||
debug := false
|
||||
command.BoolFlag("debug", "Retains debug data in the compiled application", &debug)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
quiet := verbosity == 0
|
||||
@@ -160,13 +167,20 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
}
|
||||
}
|
||||
|
||||
mode := build.Production
|
||||
modeString := "Production"
|
||||
if debug {
|
||||
mode = build.Debug
|
||||
modeString = "Debug"
|
||||
}
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
OutputFile: outputFilename,
|
||||
CleanBuildDirectory: cleanBuildDirectory,
|
||||
Mode: build.Production,
|
||||
Mode: mode,
|
||||
Pack: !noPackage,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
@@ -202,6 +216,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
fmt.Fprintf(w, "Platform: \t%s\n", buildOptions.Platform)
|
||||
fmt.Fprintf(w, "Arch: \t%s\n", buildOptions.Arch)
|
||||
fmt.Fprintf(w, "Compiler: \t%s\n", compilerPath)
|
||||
fmt.Fprintf(w, "Build Mode: \t%s\n", modeString)
|
||||
fmt.Fprintf(w, "Skip Frontend: \t%t\n", skipFrontend)
|
||||
fmt.Fprintf(w, "Compress: \t%t\n", buildOptions.Compress)
|
||||
fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
|
||||
@@ -214,7 +229,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
fmt.Fprintf(w, "\n")
|
||||
w.Flush()
|
||||
|
||||
err = checkGoModVersion(logger)
|
||||
err = checkGoModVersion(logger, updateGoMod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -243,7 +258,7 @@ func doBuild(buildOptions *build.Options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkGoModVersion(logger *clilogger.CLILogger) error {
|
||||
func checkGoModVersion(logger *clilogger.CLILogger, updateGoMod bool) error {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -265,6 +280,36 @@ func checkGoModVersion(logger *clilogger.CLILogger) error {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Println("Warning: go.mod is using Wails '%s' but the CLI is '%s'. Consider updating it.\n", gomodversion.String(), internal.Version)
|
||||
if updateGoMod {
|
||||
return syncGoModVersion(cwd)
|
||||
}
|
||||
|
||||
logger.Println("Warning: go.mod is using Wails '%s' but the CLI is '%s'. Consider updating your project's `go.mod` file.\n", gomodversion.String(), internal.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func LogGreen(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Green(text))
|
||||
}
|
||||
|
||||
func syncGoModVersion(cwd string) error {
|
||||
gomodFilename := filepath.Join(cwd, "go.mod")
|
||||
gomodData, err := os.ReadFile(gomodFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outOfSync, err := gomod.GoModOutOfSync(gomodData, internal.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !outOfSync {
|
||||
return nil
|
||||
}
|
||||
LogGreen("Updating go.mod to use Wails '%s'", internal.Version)
|
||||
newGoData, err := gomod.UpdateGoModVersion(gomodData, internal.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(gomodFilename, newGoData, 0755)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package internal
|
||||
|
||||
var Version = "v2.0.0-beta.25"
|
||||
var Version = "v2.0.0-beta.27"
|
||||
|
||||
@@ -540,6 +540,10 @@
|
||||
|
||||
// Setup callback handler
|
||||
[dialog beginSheetModalForWindow:self.mainWindow completionHandler:^(NSModalResponse returnCode) {
|
||||
if ( returnCode != NSModalResponseOK) {
|
||||
processOpenFileDialogResponse("[]");
|
||||
return;
|
||||
}
|
||||
NSMutableArray *arr = [NSMutableArray new];
|
||||
for (NSURL *url in [dialog URLs]) {
|
||||
[arr addObject:[url path]];
|
||||
@@ -558,7 +562,7 @@
|
||||
|
||||
|
||||
// Create the dialog
|
||||
NSSavePanel *dialog = [NSOpenPanel savePanel];
|
||||
NSSavePanel *dialog = [NSSavePanel savePanel];
|
||||
|
||||
// Valid but appears to do nothing.... :/
|
||||
if( title != nil ) {
|
||||
@@ -592,10 +596,12 @@
|
||||
|
||||
// Setup callback handler
|
||||
[dialog beginSheetModalForWindow:self.mainWindow completionHandler:^(NSModalResponse returnCode) {
|
||||
NSURL *url = [dialog URL];
|
||||
if ( url != nil ) {
|
||||
processSaveFileDialogResponse([url.path UTF8String]);
|
||||
return;
|
||||
if ( returnCode == NSModalResponseOK ) {
|
||||
NSURL *url = [dialog URL];
|
||||
if ( url != nil ) {
|
||||
processSaveFileDialogResponse([url.path UTF8String]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
processSaveFileDialogResponse("");
|
||||
}];
|
||||
|
||||
32
v2/internal/frontend/desktop/linux/calloc.go
Normal file
32
v2/internal/frontend/desktop/linux/calloc.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package linux
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Calloc handles alloc/dealloc of C data
|
||||
type Calloc struct {
|
||||
pool []unsafe.Pointer
|
||||
}
|
||||
|
||||
// NewCalloc creates a new allocator
|
||||
func NewCalloc() Calloc {
|
||||
return Calloc{}
|
||||
}
|
||||
|
||||
// String creates a new C string and retains a reference to it
|
||||
func (c Calloc) String(in string) *C.char {
|
||||
result := C.CString(in)
|
||||
c.pool = append(c.pool, unsafe.Pointer(result))
|
||||
return result
|
||||
}
|
||||
|
||||
// Free frees all allocated C memory
|
||||
func (c Calloc) Free() {
|
||||
for _, str := range c.pool {
|
||||
C.free(str)
|
||||
}
|
||||
c.pool = []unsafe.Pointer{}
|
||||
}
|
||||
@@ -3,10 +3,24 @@
|
||||
|
||||
package linux
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/frontend"
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
)
|
||||
import "C"
|
||||
|
||||
func (f *Frontend) OpenFileDialog(dialogOptions frontend.OpenDialogOptions) (string, error) {
|
||||
panic("implement me")
|
||||
var openFileResults = make(chan string)
|
||||
|
||||
func (f *Frontend) OpenFileDialog(dialogOptions frontend.OpenDialogOptions) (result string, err error) {
|
||||
|
||||
f.dispatch(func() {
|
||||
println("Before OpenFileDialog")
|
||||
f.mainWindow.OpenFileDialog(dialogOptions)
|
||||
println("After OpenFileDialog")
|
||||
})
|
||||
println("Waiting for result")
|
||||
result = <-openFileResults
|
||||
println("Got result")
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Frontend) OpenMultipleFilesDialog(dialogOptions frontend.OpenDialogOptions) ([]string, error) {
|
||||
@@ -24,3 +38,8 @@ func (f *Frontend) SaveFileDialog(dialogOptions frontend.SaveDialogOptions) (str
|
||||
func (f *Frontend) MessageDialog(dialogOptions frontend.MessageDialogOptions) (string, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
//export processOpenFileResult
|
||||
func processOpenFileResult(result *C.char) {
|
||||
openFileResults <- C.GoString(result)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -62,6 +63,7 @@ type Frontend struct {
|
||||
|
||||
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
|
||||
|
||||
println("[NewFrontend] PID:", os.Getpid())
|
||||
// Set GDK_BACKEND=x11 to prevent warnings
|
||||
os.Setenv("GDK_BACKEND", "x11")
|
||||
|
||||
@@ -301,10 +303,11 @@ var dispatchCallbackLock sync.Mutex
|
||||
|
||||
//export callDispatchedMethod
|
||||
func callDispatchedMethod(cid C.int) {
|
||||
println("[callDispatchedMethod] PID:", os.Getpid())
|
||||
id := int(cid)
|
||||
fn := dispatchCallbacks[id]
|
||||
if fn != nil {
|
||||
go fn()
|
||||
fn()
|
||||
dispatchCallbackLock.Lock()
|
||||
delete(dispatchCallbacks, id)
|
||||
dispatchCallbackLock.Unlock()
|
||||
@@ -342,11 +345,24 @@ func (f *Frontend) processRequest(request unsafe.Pointer) {
|
||||
|
||||
// Load file from asset store
|
||||
content, mimeType, err := f.assets.Load(file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO How to return 404/500 errors to webkit?
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
f.dispatch(func() {
|
||||
message := C.CString("not found")
|
||||
defer C.free(unsafe.Pointer(message))
|
||||
C.webkit_uri_scheme_request_finish_error(req, C.g_error_new_literal(C.G_FILE_ERROR_NOENT, C.int(404), message))
|
||||
})
|
||||
} else {
|
||||
err = fmt.Errorf("Error processing request %s: %w", uri, err)
|
||||
f.logger.Error(err.Error())
|
||||
message := C.CString("internal server error")
|
||||
defer C.free(unsafe.Pointer(message))
|
||||
C.webkit_uri_scheme_request_finish_error(req, C.g_error_new_literal(C.G_FILE_ERROR_NOENT, C.int(500), message))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
cContent := C.CString(string(content))
|
||||
defer C.free(unsafe.Pointer(cContent))
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
package linux
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 x11
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
#include "webkit2/webkit2.h"
|
||||
#include <X11/Xlib.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
|
||||
@@ -83,7 +84,7 @@ extern void processMessage(char*);
|
||||
|
||||
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
||||
WebKitJavascriptResult *result,
|
||||
void*)
|
||||
void* data)
|
||||
{
|
||||
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
|
||||
JSCValue *value = webkit_javascript_result_get_js_value(result);
|
||||
@@ -105,6 +106,11 @@ ulong setupInvokeSignal(void* contentManager) {
|
||||
return g_signal_connect((WebKitUserContentManager*)contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), NULL);
|
||||
}
|
||||
|
||||
void initThreads() {
|
||||
printf("init threads\n");
|
||||
XInitThreads();
|
||||
}
|
||||
|
||||
// These are the x,y & time of the last mouse down event
|
||||
// It's used for window dragging
|
||||
float xroot = 0.0f;
|
||||
@@ -145,7 +151,7 @@ void connectButtons(void* webview) {
|
||||
extern void processURLRequest(WebKitURISchemeRequest *request);
|
||||
|
||||
// This is called when the close button on the window is pressed
|
||||
gboolean close_button_pressed(GtkWidget *widget, GdkEvent *event, void*)
|
||||
gboolean close_button_pressed(GtkWidget *widget, GdkEvent *event, void* data)
|
||||
{
|
||||
processMessage("Q");
|
||||
return FALSE;
|
||||
@@ -184,10 +190,36 @@ static void startDrag(void *webview, GtkWindow* mainwindow)
|
||||
gtk_window_begin_move_drag(mainwindow, 1, xroot, yroot, dragTime);
|
||||
}
|
||||
|
||||
void extern processOpenFileResult(char*);
|
||||
|
||||
static void OpenDialog(GtkWindow* window, char *title) {
|
||||
printf("Here\n");
|
||||
GtkWidget *dlg = gtk_file_chooser_dialog_new(title, window, GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Open", GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
printf("Here3\n");
|
||||
|
||||
gint response = gtk_dialog_run(GTK_DIALOG(dlg));
|
||||
printf("Here 4\n");
|
||||
|
||||
if (response == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
printf("Here 5\n");
|
||||
|
||||
gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
|
||||
processOpenFileResult(filename);
|
||||
g_free(filename);
|
||||
}
|
||||
gtk_widget_destroy(dlg);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/frontend"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -369,6 +401,8 @@ func (w *Window) Run() {
|
||||
case options.Maximised:
|
||||
w.Maximise()
|
||||
}
|
||||
|
||||
C.initThreads()
|
||||
C.gtk_main()
|
||||
w.Destroy()
|
||||
}
|
||||
@@ -408,3 +442,11 @@ func (w *Window) StartDrag() {
|
||||
func (w *Window) Quit() {
|
||||
C.gtk_main_quit()
|
||||
}
|
||||
|
||||
func (w *Window) OpenFileDialog(dialogOptions frontend.OpenDialogOptions) {
|
||||
println("OpenFileDialog PID:", os.Getpid())
|
||||
mem := NewCalloc()
|
||||
title := mem.String(dialogOptions.Title)
|
||||
C.OpenDialog(w.asGTKWindow(), title)
|
||||
mem.Free()
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export function EventsEmit(eventName) {
|
||||
|
||||
export function EventsOff(eventName) {
|
||||
// Remove local listeners
|
||||
eventListeners.delete(eventName);
|
||||
delete eventListeners[eventName];
|
||||
|
||||
// Notify Go listeners
|
||||
window.WailsInvoke('EX' + eventName);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
v2/internal/webview2runtime/MicrosoftEdgeWebview2Setup.exe
Normal file
BIN
v2/internal/webview2runtime/MicrosoftEdgeWebview2Setup.exe
Normal file
Binary file not shown.
232
v2/internal/webview2runtime/sudo_mattn.go
Normal file
232
v2/internal/webview2runtime/sudo_mattn.go
Normal file
@@ -0,0 +1,232 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// Original File (c) 2017 Yasuhiro Matsumoto: https://github.com/mattn/sudo/blob/master/win32.go
|
||||
// License: https://github.com/mattn/sudo/blob/master/LICENSE
|
||||
|
||||
package webview2runtime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
modshell32 = syscall.NewLazyDLL("shell32.dll")
|
||||
procShellExecuteEx = modshell32.NewProc("ShellExecuteExW")
|
||||
)
|
||||
|
||||
const (
|
||||
_SEE_MASK_DEFAULT = 0x00000000
|
||||
_SEE_MASK_CLASSNAME = 0x00000001
|
||||
_SEE_MASK_CLASSKEY = 0x00000003
|
||||
_SEE_MASK_IDLIST = 0x00000004
|
||||
_SEE_MASK_INVOKEIDLIST = 0x0000000C
|
||||
_SEE_MASK_ICON = 0x00000010
|
||||
_SEE_MASK_HOTKEY = 0x00000020
|
||||
_SEE_MASK_NOCLOSEPROCESS = 0x00000040
|
||||
_SEE_MASK_CONNECTNETDRV = 0x00000080
|
||||
_SEE_MASK_NOASYNC = 0x00000100
|
||||
_SEE_MASK_FLAG_DDEWAIT = 0x00000100
|
||||
_SEE_MASK_DOENVSUBST = 0x00000200
|
||||
_SEE_MASK_FLAG_NO_UI = 0x00000400
|
||||
_SEE_MASK_UNICODE = 0x00004000
|
||||
_SEE_MASK_NO_CONSOLE = 0x00008000
|
||||
_SEE_MASK_ASYNCOK = 0x00100000
|
||||
_SEE_MASK_NOQUERYCLASSSTORE = 0x01000000
|
||||
_SEE_MASK_HMONITOR = 0x00200000
|
||||
_SEE_MASK_NOZONECHECKS = 0x00800000
|
||||
_SEE_MASK_WAITFORINPUTIDLE = 0x02000000
|
||||
_SEE_MASK_FLAG_LOG_USAGE = 0x04000000
|
||||
_SEE_MASK_FLAG_HINST_IS_SITE = 0x08000000
|
||||
)
|
||||
|
||||
const (
|
||||
_ERROR_BAD_FORMAT = 11
|
||||
)
|
||||
|
||||
const (
|
||||
_SE_ERR_FNF = 2
|
||||
_SE_ERR_PNF = 3
|
||||
_SE_ERR_ACCESSDENIED = 5
|
||||
_SE_ERR_OOM = 8
|
||||
_SE_ERR_DLLNOTFOUND = 32
|
||||
_SE_ERR_SHARE = 26
|
||||
_SE_ERR_ASSOCINCOMPLETE = 27
|
||||
_SE_ERR_DDETIMEOUT = 28
|
||||
_SE_ERR_DDEFAIL = 29
|
||||
_SE_ERR_DDEBUSY = 30
|
||||
_SE_ERR_NOASSOC = 31
|
||||
)
|
||||
|
||||
type (
|
||||
dword uint32
|
||||
hinstance syscall.Handle
|
||||
hkey syscall.Handle
|
||||
hwnd syscall.Handle
|
||||
ulong uint32
|
||||
lpctstr uintptr
|
||||
lpvoid uintptr
|
||||
)
|
||||
|
||||
// SHELLEXECUTEINFO struct
|
||||
type _SHELLEXECUTEINFO struct {
|
||||
cbSize dword
|
||||
fMask ulong
|
||||
hwnd hwnd
|
||||
lpVerb lpctstr
|
||||
lpFile lpctstr
|
||||
lpParameters lpctstr
|
||||
lpDirectory lpctstr
|
||||
nShow int
|
||||
hInstApp hinstance
|
||||
lpIDList lpvoid
|
||||
lpClass lpctstr
|
||||
hkeyClass hkey
|
||||
dwHotKey dword
|
||||
hIconOrMonitor syscall.Handle
|
||||
hProcess syscall.Handle
|
||||
}
|
||||
|
||||
// ShellExecuteAndWait is version of ShellExecuteEx which want process
|
||||
func ShellExecuteAndWait(hwnd hwnd, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
|
||||
var lpctstrVerb, lpctstrParameters, lpctstrDirectory, lpctstrFile lpctstr
|
||||
var err error
|
||||
if len(lpOperation) != 0 {
|
||||
lpctstrVerb, err = toUTF16(lpOperation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(lpParameters) != 0 {
|
||||
lpctstrParameters, err = toUTF16(lpParameters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(lpDirectory) != 0 {
|
||||
lpctstrDirectory, err = toUTF16(lpDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(lpDirectory) != 0 {
|
||||
lpctstrFile, err = toUTF16(lpFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
i := &_SHELLEXECUTEINFO{
|
||||
fMask: _SEE_MASK_NOCLOSEPROCESS,
|
||||
hwnd: hwnd,
|
||||
lpVerb: lpctstrVerb,
|
||||
lpFile: lpctstrFile,
|
||||
lpParameters: lpctstrParameters,
|
||||
lpDirectory: lpctstrDirectory,
|
||||
nShow: nShowCmd,
|
||||
}
|
||||
i.cbSize = dword(unsafe.Sizeof(*i))
|
||||
return ShellExecuteEx(i)
|
||||
}
|
||||
|
||||
func toUTF16(lpOperation string) (lpctstr, error) {
|
||||
result, err := syscall.UTF16PtrFromString(lpOperation)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return lpctstr(unsafe.Pointer(result)), nil
|
||||
}
|
||||
|
||||
// ShellExecuteNoWait is version of ShellExecuteEx which don't want process
|
||||
func ShellExecuteNowait(hwnd hwnd, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
|
||||
var lpctstrVerb, lpctstrParameters, lpctstrDirectory, lpctstrFile lpctstr
|
||||
var err error
|
||||
if len(lpOperation) != 0 {
|
||||
lpctstrVerb, err = toUTF16(lpOperation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(lpParameters) != 0 {
|
||||
lpctstrParameters, err = toUTF16(lpParameters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(lpDirectory) != 0 {
|
||||
lpctstrDirectory, err = toUTF16(lpDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(lpDirectory) != 0 {
|
||||
lpctstrFile, err = toUTF16(lpFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
i := &_SHELLEXECUTEINFO{
|
||||
fMask: _SEE_MASK_DEFAULT,
|
||||
hwnd: hwnd,
|
||||
lpVerb: lpctstrVerb,
|
||||
lpFile: lpctstrFile,
|
||||
lpParameters: lpctstrParameters,
|
||||
lpDirectory: lpctstrDirectory,
|
||||
nShow: nShowCmd,
|
||||
}
|
||||
i.cbSize = dword(unsafe.Sizeof(*i))
|
||||
return ShellExecuteEx(i)
|
||||
}
|
||||
|
||||
// ShellExecuteEx is Windows API
|
||||
func ShellExecuteEx(pExecInfo *_SHELLEXECUTEINFO) error {
|
||||
ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(pExecInfo)))
|
||||
if ret == 1 && pExecInfo.fMask&_SEE_MASK_NOCLOSEPROCESS != 0 {
|
||||
s, e := syscall.WaitForSingleObject(pExecInfo.hProcess, syscall.INFINITE)
|
||||
switch s {
|
||||
case syscall.WAIT_OBJECT_0:
|
||||
break
|
||||
case syscall.WAIT_FAILED:
|
||||
return os.NewSyscallError("WaitForSingleObject", e)
|
||||
default:
|
||||
return errors.New("Unexpected result from WaitForSingleObject")
|
||||
}
|
||||
}
|
||||
errorMsg := ""
|
||||
if pExecInfo.hInstApp != 0 && pExecInfo.hInstApp <= 32 {
|
||||
switch int(pExecInfo.hInstApp) {
|
||||
case _SE_ERR_FNF:
|
||||
errorMsg = "The specified file was not found"
|
||||
case _SE_ERR_PNF:
|
||||
errorMsg = "The specified path was not found"
|
||||
case _ERROR_BAD_FORMAT:
|
||||
errorMsg = "The .exe file is invalid (non-Win32 .exe or error in .exe image)"
|
||||
case _SE_ERR_ACCESSDENIED:
|
||||
errorMsg = "The operating system denied access to the specified file"
|
||||
case _SE_ERR_ASSOCINCOMPLETE:
|
||||
errorMsg = "The file name association is incomplete or invalid"
|
||||
case _SE_ERR_DDEBUSY:
|
||||
errorMsg = "The DDE transaction could not be completed because other DDE transactions were being processed"
|
||||
case _SE_ERR_DDEFAIL:
|
||||
errorMsg = "The DDE transaction failed"
|
||||
case _SE_ERR_DDETIMEOUT:
|
||||
errorMsg = "The DDE transaction could not be completed because the request timed out"
|
||||
case _SE_ERR_DLLNOTFOUND:
|
||||
errorMsg = "The specified DLL was not found"
|
||||
case _SE_ERR_NOASSOC:
|
||||
errorMsg = "There is no application associated with the given file name extension"
|
||||
case _SE_ERR_OOM:
|
||||
errorMsg = "There was not enough memory to complete the operation"
|
||||
case _SE_ERR_SHARE:
|
||||
errorMsg = "A sharing violation occurred"
|
||||
default:
|
||||
errorMsg = fmt.Sprintf("Unknown error occurred with error code %v", pExecInfo.hInstApp)
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return errors.New(errorMsg)
|
||||
}
|
||||
168
v2/internal/webview2runtime/webview2runtime.go
Normal file
168
v2/internal/webview2runtime/webview2runtime.go
Normal file
@@ -0,0 +1,168 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package webview2runtime
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//go:embed MicrosoftEdgeWebview2Setup.exe
|
||||
var setupexe []byte
|
||||
|
||||
// Info contains all the information about an installation of the webview2 runtime.
|
||||
type Info struct {
|
||||
Location string
|
||||
Name string
|
||||
Version string
|
||||
SilentUninstall string
|
||||
}
|
||||
|
||||
// IsOlderThan returns true if the installed version is older than the given required version.
|
||||
// Returns error if something goes wrong.
|
||||
func (i *Info) IsOlderThan(requiredVersion string) (bool, error) {
|
||||
var mod = syscall.NewLazyDLL("WebView2Loader.dll")
|
||||
var CompareBrowserVersions = mod.NewProc("CompareBrowserVersions")
|
||||
v1, err := syscall.UTF16PtrFromString(i.Version)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
v2, err := syscall.UTF16PtrFromString(requiredVersion)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var result int = 9
|
||||
_, _, err = CompareBrowserVersions.Call(uintptr(unsafe.Pointer(v1)), uintptr(unsafe.Pointer(v2)), uintptr(unsafe.Pointer(&result)))
|
||||
if result < -1 || result > 1 {
|
||||
return false, err
|
||||
}
|
||||
return result == -1, nil
|
||||
}
|
||||
|
||||
func downloadBootstrapper() (string, error) {
|
||||
bootstrapperURL := `https://go.microsoft.com/fwlink/p/?LinkId=2124703`
|
||||
installer := filepath.Join(os.TempDir(), `MicrosoftEdgeWebview2Setup.exe`)
|
||||
|
||||
// Download installer
|
||||
out, err := os.Create(installer)
|
||||
defer out.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := http.Get(bootstrapperURL)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
err = out.Close()
|
||||
return "", err
|
||||
}
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return installer, nil
|
||||
}
|
||||
|
||||
// InstallUsingEmbeddedBootstrapper will download the bootstrapper from Microsoft and run it to install
|
||||
// the latest version of the runtime.
|
||||
// Returns true if the installer ran successfully.
|
||||
// Returns an error if something goes wrong
|
||||
func InstallUsingEmbeddedBootstrapper() (bool, error) {
|
||||
|
||||
installer := filepath.Join(os.TempDir(), `MicrosoftEdgeWebview2Setup.exe`)
|
||||
err := os.WriteFile(installer, setupexe, 0755)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
result, err := runInstaller(installer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return result, os.Remove(installer)
|
||||
|
||||
}
|
||||
|
||||
// InstallUsingBootstrapper will extract the embedded bootstrapper from Microsoft and run it to install
|
||||
// the latest version of the runtime.
|
||||
// Returns true if the installer ran successfully.
|
||||
// Returns an error if something goes wrong
|
||||
func InstallUsingBootstrapper() (bool, error) {
|
||||
|
||||
installer, err := downloadBootstrapper()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
result, err := runInstaller(installer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return result, os.Remove(installer)
|
||||
|
||||
}
|
||||
|
||||
func runInstaller(installer string) (bool, error) {
|
||||
err := ShellExecuteAndWait(0, "runas", installer, "", os.Getenv("TMP"), syscall.SW_NORMAL)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Confirm will prompt the user with a message and OK / CANCEL buttons.
|
||||
// Returns true if OK is selected by the user.
|
||||
// Returns an error if something went wrong.
|
||||
func Confirm(caption string, title string) (bool, error) {
|
||||
var flags uint = 0x00000001 // MB_OKCANCEL
|
||||
result, err := MessageBox(caption, title, flags)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return result == 1, nil
|
||||
}
|
||||
|
||||
// Error will an error message to the user.
|
||||
// Returns an error if something went wrong.
|
||||
func Error(caption string, title string) error {
|
||||
var flags uint = 0x00000010 // MB_ICONERROR
|
||||
_, err := MessageBox(caption, title, flags)
|
||||
return err
|
||||
}
|
||||
|
||||
// MessageBox prompts the user with the given caption and title.
|
||||
// Flags may be provided to customise the dialog.
|
||||
// Returns an error if something went wrong.
|
||||
func MessageBox(caption string, title string, flags uint) (int, error) {
|
||||
captionUTF16, err := syscall.UTF16PtrFromString(caption)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
titleUTF16, err := syscall.UTF16PtrFromString(title)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
|
||||
uintptr(0),
|
||||
uintptr(unsafe.Pointer(captionUTF16)),
|
||||
uintptr(unsafe.Pointer(titleUTF16)),
|
||||
uintptr(flags))
|
||||
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
// OpenInstallerDownloadWebpage will open the browser on the WebView2 download page
|
||||
func OpenInstallerDownloadWebpage() error {
|
||||
cmd := exec.Command("rundll32", "url.dll,FileProtocolHandler", "https://developer.microsoft.com/en-us/microsoft-edge/webview2/")
|
||||
return cmd.Run()
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
commands := slicer.String([]string{"build"})
|
||||
|
||||
// Add better debugging flags
|
||||
if options.Mode == Dev {
|
||||
if options.Mode == Dev || options.Mode == Debug {
|
||||
commands.Add("-gcflags")
|
||||
commands.Add(`"all=-N -l"`)
|
||||
}
|
||||
@@ -233,7 +233,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
tags.Add(options.WebView2Strategy)
|
||||
}
|
||||
|
||||
if options.Mode == Production {
|
||||
if options.Mode == Production || options.Mode == Debug {
|
||||
tags.Add("production")
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ const (
|
||||
Dev Mode = iota
|
||||
// Production mode
|
||||
Production
|
||||
// Debug build
|
||||
Debug
|
||||
)
|
||||
|
||||
// Options contains all the build options as well as the project data
|
||||
|
||||
@@ -66,6 +66,8 @@ A list of community maintained templates can be found [here](/docs/community/tem
|
||||
| -upxflags | Flags to pass to upx | |
|
||||
| -v int | Verbosity level (0 - silent, 1 - default, 2 - verbose) | 1 |
|
||||
| -webview2 | WebView2 installer strategy: download,embed,browser,error | download |
|
||||
| -u | Updates your project's `go.mod` to use the same version of Wails as the CLI | |
|
||||
| -debug | Retains debug information in the application | false |
|
||||
|
||||
For a detailed description of the `webview2` flag, please refer to the [Windows](/docs/guides/windows) Guide.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user