5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-10 22:19:46 +08:00

Merge remote-tracking branch 'origin/v3-alpha' into v3-alpha

This commit is contained in:
Lea Anthony 2023-05-05 06:41:04 +10:00
commit f5557c612a
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
105 changed files with 10907 additions and 516 deletions

View File

@ -22,6 +22,7 @@ import (
"github.com/wailsapp/wails/v2/cmd/wails/flags"
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
"golang.org/x/mod/semver"
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
@ -36,6 +37,10 @@ import (
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
const (
viteMinVersion = "v3.0.0"
)
func sliceToMap(input []string) map[string]struct{} {
result := map[string]struct{}{}
for _, value := range input {
@ -88,10 +93,11 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
buildOptions.IgnoreApplication = false
}
legacyUseDevServerInsteadofCustomScheme := false
// frontend:dev:watcher command.
frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery()
if command := projectConfig.DevWatcherCommand; command != "" {
closer, devServerURL, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
closer, devServerURL, devServerViteVersion, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
if err != nil {
return err
}
@ -100,6 +106,12 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
f.FrontendDevServerURL = devServerURL
}
defer closer()
if devServerViteVersion != "" && semver.Compare(devServerViteVersion, viteMinVersion) < 0 {
logutils.LogRed("Please upgrade your Vite Server to at least '%s' future Wails versions will require at least Vite '%s'", viteMinVersion, viteMinVersion)
time.Sleep(3 * time.Second)
legacyUseDevServerInsteadofCustomScheme = true
}
} else if frontendDevAutoDiscovery {
return fmt.Errorf("unable to auto discover frontend:dev:serverUrl without a frontend:dev:watcher command, please either set frontend:dev:watcher or remove the auto discovery from frontend:dev:serverUrl")
}
@ -107,7 +119,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
// Do initial build but only for the application.
logger.Println("Building application for development...")
buildOptions.IgnoreFrontend = true
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel)
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
buildOptions.IgnoreFrontend = ignoreFrontend || f.FrontendDevServerURL != ""
if err != nil {
return err
@ -153,7 +165,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
}()
// Watch for changes and trigger restartApp()
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL())
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL(), legacyUseDevServerInsteadofCustomScheme)
// Kill the current program if running and remove dev binary
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
@ -202,7 +214,7 @@ func runCommand(dir string, exitOnError bool, command string, args ...string) er
}
// runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev`
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, error) {
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, string, error) {
ctx, cancel := context.WithCancel(context.Background())
scanner := NewStdoutScanner()
cmdSlice := strings.Split(devCommand, " ")
@ -214,7 +226,7 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
if err := cmd.Start(); err != nil {
cancel()
return nil, "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
return nil, "", "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
}
var viteServerURL string
@ -224,10 +236,19 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
viteServerURL = serverURL
case <-time.After(time.Second * 10):
cancel()
return nil, "", errors.New("failed to find Vite server URL")
return nil, "", "", errors.New("failed to find Vite server URL")
}
}
viteVersion := ""
select {
case version := <-scanner.ViteServerVersionC:
viteVersion = version
case <-time.After(time.Second * 5):
// That's fine, then most probably it was not vite that was running
}
logutils.LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
var wg sync.WaitGroup
wg.Add(1)
@ -255,11 +276,11 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
}
cancel()
wg.Wait()
}, viteServerURL, nil
}, viteServerURL, viteVersion, nil
}
// restartApp does the actual rebuilding of the application when files change
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int) (*process.Process, string, error) {
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int, legacyUseDevServerInsteadofCustomScheme bool) (*process.Process, string, error) {
appBinary, err := build.Build(buildOptions)
println()
@ -297,6 +318,9 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
os.Setenv("assetdir", f.AssetDir)
os.Setenv("devserver", f.DevServer)
os.Setenv("frontenddevserverurl", f.FrontendDevServerURL)
if legacyUseDevServerInsteadofCustomScheme {
os.Setenv("legacyusedevsererinsteadofcustomscheme", "true")
}
// Start up new binary with correct args
newProcess := process.NewProcess(appBinary, args...)
@ -316,7 +340,7 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
}
// doWatcherLoop is the main watch loop that runs while dev is active
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) *process.Process {
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL, legacyUseDevServerInsteadofCustomScheme bool) *process.Process {
// Main Loop
var extensionsThatTriggerARebuild = sliceToMap(strings.Split(f.Extensions, ","))
var dirsThatTriggerAReload []string
@ -422,7 +446,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
rebuild = false
logutils.LogGreen("[Rebuild triggered] files updated")
// Try and build the app
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel)
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
if err != nil {
logutils.LogRed("Error during build: %s", err.Error())
continue

View File

@ -2,30 +2,47 @@ package dev
import (
"bufio"
"fmt"
"net/url"
"os"
"strings"
"github.com/acarl005/stripansi"
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
"golang.org/x/mod/semver"
)
// stdoutScanner acts as a stdout target that will scan the incoming
// data to find out the vite server url
type stdoutScanner struct {
ViteServerURLChan chan string
ViteServerURLChan chan string
ViteServerVersionC chan string
versionDetected bool
}
// NewStdoutScanner creates a new stdoutScanner
func NewStdoutScanner() *stdoutScanner {
return &stdoutScanner{
ViteServerURLChan: make(chan string, 2),
ViteServerURLChan: make(chan string, 2),
ViteServerVersionC: make(chan string, 2),
}
}
// Write bytes to the scanner. Will copy the bytes to stdout
func (s *stdoutScanner) Write(data []byte) (n int, err error) {
input := stripansi.Strip(string(data))
if !s.versionDetected {
v, err := detectViteVersion(input)
if v != "" || err != nil {
if err != nil {
logutils.LogRed("ViteStdoutScanner: %s", err)
v = "v0.0.0"
}
s.ViteServerVersionC <- v
s.versionDetected = true
}
}
match := strings.Index(input, "Local:")
if match != -1 {
sc := bufio.NewScanner(strings.NewReader(input))
@ -47,3 +64,21 @@ func (s *stdoutScanner) Write(data []byte) (n int, err error) {
}
return os.Stdout.Write(data)
}
func detectViteVersion(line string) (string, error) {
s := strings.Split(strings.TrimSpace(line), " ")
if strings.ToLower(s[0]) != "vite" {
return "", nil
}
if len(line) < 2 {
return "", fmt.Errorf("unable to parse vite version")
}
v := s[1]
if !semver.IsValid(v) {
return "", fmt.Errorf("%s is not a valid vite version string", v)
}
return v, nil
}

View File

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

View File

@ -8,9 +8,11 @@ import (
"flag"
"fmt"
iofs "io/fs"
"net"
"net/url"
"os"
"path/filepath"
"time"
"github.com/wailsapp/wails/v2/pkg/assetserver"
@ -104,17 +106,35 @@ func CreateApp(appoptions *options.App) (*App, error) {
}
if frontendDevServerURL != "" {
if devServer == "" {
return nil, fmt.Errorf("Unable to use FrontendDevServerUrl without a DevServer address")
if os.Getenv("legacyusedevsererinsteadofcustomscheme") != "" {
startURL, err := url.Parse("http://" + devServer)
if err != nil {
return nil, err
}
ctx = context.WithValue(ctx, "starturl", startURL)
}
startURL, err := url.Parse("http://" + devServer)
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
externalURL, err := url.Parse(frontendDevServerURL)
if err != nil {
return nil, err
}
ctx = context.WithValue(ctx, "starturl", startURL)
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
if externalURL.Host == "" {
return nil, fmt.Errorf("Invalid frontend:dev:serverUrl missing protocol scheme?")
}
waitCb := func() { myLogger.Debug("Waiting for frontend DevServer '%s' to be ready", externalURL) }
if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) {
myLogger.Error("Timeout waiting for frontend DevServer")
}
handler := assetserver.NewExternalAssetsHandler(myLogger, assetConfig, externalURL)
assetConfig.Assets = nil
assetConfig.Handler = handler
assetConfig.Middleware = nil
myLogger.Info("Serving assets from frontend DevServer URL: %s", frontendDevServerURL)
} else {
@ -246,3 +266,22 @@ func tryInferAssetDirFromFS(assets iofs.FS) (string, error) {
return path, nil
}
func checkPortIsOpen(host string, timeout time.Duration, waitCB func()) (ret bool) {
if timeout == 0 {
timeout = time.Minute
}
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
conn, _ := net.DialTimeout("tcp", host, 2*time.Second)
if conn != nil {
conn.Close()
return true
}
waitCB()
time.Sleep(1 * time.Second)
}
return false
}

View File

@ -12,5 +12,6 @@ typedef int Role;
static const Role AppMenu = 1;
static const Role EditMenu = 2;
static const Role WindowMenu = 3;
#endif /* Role_h */

View File

@ -68,12 +68,20 @@
appName = [[NSProcessInfo processInfo] processName];
}
WailsMenu *appMenu = [[[WailsMenu new] initWithNSTitle:appName] autorelease];
if (ctx.aboutTitle != nil) {
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
[appMenu addItem:[NSMenuItem separatorItem]];
}
[appMenu addItem:[self newMenuItem:[@"Hide " stringByAppendingString:appName] :@selector(hide:) :@"h" :NSEventModifierFlagCommand]];
[appMenu addItem:[self newMenuItem:@"Hide Others" :@selector(hideOtherApplications:) :@"h" :(NSEventModifierFlagOption | NSEventModifierFlagCommand)]];
[appMenu addItem:[self newMenuItem:@"Show All" :@selector(unhideAllApplications:) :@""]];
[appMenu addItem:[NSMenuItem separatorItem]];
id quitTitle = [@"Quit " stringByAppendingString:appName];
NSMenuItem* quitMenuItem = [self newMenuItem:quitTitle :@selector(Quit) :@"q" :NSEventModifierFlagCommand];
quitMenuItem.target = ctx;
if (ctx.aboutTitle != nil) {
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
}
[appMenu addItem:quitMenuItem];
[self appendSubmenu:appMenu];
break;
@ -100,6 +108,17 @@
[editMenu appendSubmenu:speechMenu];
[self appendSubmenu:editMenu];
break;
}
case WindowMenu:
{
WailsMenu *windowMenu = [[[WailsMenu new] initWithNSTitle:@"Window"] autorelease];
[windowMenu addItem:[self newMenuItem:@"Minimize" :@selector(performMiniaturize:) :@"m" :NSEventModifierFlagCommand]];
[windowMenu addItem:[self newMenuItem:@"Zoom" :@selector(performZoom:) :@""]];
[windowMenu addItem:[NSMenuItem separatorItem]];
[windowMenu addItem:[self newMenuItem:@"Full Screen" :@selector(enterFullScreenMode:) :@"f" :(NSEventModifierFlagControl | NSEventModifierFlagCommand)]];
[self appendSubmenu:windowMenu];
break;
}
}

View File

@ -113,7 +113,6 @@ func (f *Frontend) startMessageProcessor() {
func (f *Frontend) startRequestProcessor() {
for request := range requestBuffer {
f.assets.ServeWebViewRequest(request)
request.Release()
}
}
func (f *Frontend) startCallbackProcessor() {

View File

@ -466,7 +466,6 @@ var requestBuffer = make(chan webview.Request, 100)
func (f *Frontend) startRequestProcessor() {
for request := range requestBuffer {
f.assets.ServeWebViewRequest(request)
request.Release()
}
}

View File

@ -10,13 +10,11 @@ import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
"time"
"github.com/wailsapp/wails/v2/pkg/assetserver"
@ -67,7 +65,6 @@ func (d *DevWebServer) Run(ctx context.Context) error {
myLogger = _logger.(*logger.Logger)
}
var assetHandler http.Handler
var wsHandler http.Handler
_fronendDevServerURL, _ := ctx.Value("frontenddevserverurl").(string)
@ -77,33 +74,23 @@ func (d *DevWebServer) Run(ctx context.Context) error {
return c.String(http.StatusOK, assetdir)
})
var err error
assetHandler, err = assetserver.NewAssetHandler(assetServerConfig, myLogger)
if err != nil {
log.Fatal(err)
}
} else {
externalURL, err := url.Parse(_fronendDevServerURL)
if err != nil {
return err
}
if externalURL.Host == "" {
return fmt.Errorf("Invalid frontend:dev:serverUrl missing protocol scheme?")
}
waitCb := func() { d.LogDebug("Waiting for frontend DevServer '%s' to be ready", externalURL) }
if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) {
d.logger.Error("Timeout waiting for frontend DevServer")
}
assetHandler = newExternalDevServerAssetHandler(d.logger, externalURL, assetServerConfig)
// WebSockets aren't currently supported in prod mode, so a WebSocket connection is the result of the
// FrontendDevServer e.g. Vite to support auto reloads.
// Therefore we direct WebSockets directly to the FrontendDevServer instead of returning a NotImplementedStatus.
wsHandler = httputil.NewSingleHostReverseProxy(externalURL)
}
assetHandler, err := assetserver.NewAssetHandler(assetServerConfig, myLogger)
if err != nil {
log.Fatal(err)
}
// Setup internal dev server
bindingsJSON, err := d.appBindings.ToJSON()
if err != nil {
@ -307,22 +294,3 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
result.server.HidePort = true
return result
}
func checkPortIsOpen(host string, timeout time.Duration, waitCB func()) (ret bool) {
if timeout == 0 {
timeout = time.Minute
}
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
conn, _ := net.DialTimeout("tcp", host, 2*time.Second)
if conn != nil {
conn.Close()
return true
}
waitCB()
time.Sleep(1 * time.Second)
}
return false
}

View File

@ -1,7 +1,7 @@
//go:build dev
// +build dev
package devserver
package assetserver
import (
"errors"
@ -10,21 +10,12 @@ import (
"net/http/httputil"
"net/url"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)
func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, options assetserver.Options) http.Handler {
handler := newExternalAssetsHandler(logger, url, options.Handler)
func NewExternalAssetsHandler(logger Logger, options assetserver.Options, url *url.URL) http.Handler {
baseHandler := options.Handler
if middleware := options.Middleware; middleware != nil {
handler = middleware(handler)
}
return handler
}
func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.Handler) http.Handler {
errSkipProxy := fmt.Errorf("skip proxying")
proxy := httputil.NewSingleHostReverseProxy(url)
@ -37,7 +28,7 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
}
proxy.ModifyResponse = func(res *http.Response) error {
if handler == nil {
if baseHandler == nil {
return nil
}
@ -53,11 +44,11 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
}
proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) {
if handler != nil && errors.Is(err, errSkipProxy) {
if baseHandler != nil && errors.Is(err, errSkipProxy) {
if logger != nil {
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using AssetHandler", r.URL)
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL)
}
handler.ServeHTTP(rw, r)
baseHandler.ServeHTTP(rw, r)
} else {
if logger != nil {
logger.Error("[ExternalAssetHandler] Proxy error: %v", err)
@ -66,18 +57,24 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
}
}
return http.HandlerFunc(
var result http.Handler = http.HandlerFunc(
func(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodGet {
proxy.ServeHTTP(rw, req)
return
}
if handler != nil {
handler.ServeHTTP(rw, req)
if baseHandler != nil {
baseHandler.ServeHTTP(rw, req)
return
}
rw.WriteHeader(http.StatusMethodNotAllowed)
})
if middleware := options.Middleware; middleware != nil {
result = middleware(result)
}
return result
}

View File

@ -56,13 +56,7 @@ func (r legacyRequest) Response() webview.ResponseWriter {
return &legacyRequestNoOpCloserResponseWriter{r.rw}
}
func (r legacyRequest) AddRef() error {
return nil
}
func (r legacyRequest) Release() error {
return nil
}
func (r legacyRequest) Close() error { return nil }
func (r *legacyRequest) request() (*http.Request, error) {
if r.req != nil {
@ -81,6 +75,4 @@ type legacyRequestNoOpCloserResponseWriter struct {
http.ResponseWriter
}
func (*legacyRequestNoOpCloserResponseWriter) Finish() error {
return nil
}
func (*legacyRequestNoOpCloserResponseWriter) Finish() {}

View File

@ -22,6 +22,7 @@ type assetServerWebView struct {
// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server.
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way.
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
d.dispatchInit.Do(func() {
workers := d.dispatchWorkers
@ -33,8 +34,11 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
for i := 0; i < workers; i++ {
go func() {
for req := range workerC {
uri, _ := req.URL()
d.processWebViewRequest(req)
req.Release()
if err := req.Close(); err != nil {
d.logError("Unable to call close for request for uri '%s'", uri)
}
}
}()
}
@ -45,12 +49,6 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
d.dispatchReqC = dispatchC
})
if err := req.AddRef(); err != nil {
uri, _ := req.URL()
d.logError("Unable to call AddRef for request '%s'", uri)
return
}
d.dispatchReqC <- req
}

View File

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

View File

@ -118,11 +118,9 @@ import (
)
// NewRequest creates as new WebViewRequest based on a pointer to an `id<WKURLSchemeTask>`
//
// Please make sure to call Release() when finished using the request.
func NewRequest(wkURLSchemeTask unsafe.Pointer) Request {
C.URLSchemeTaskRetain(wkURLSchemeTask)
return &request{task: wkURLSchemeTask}
return newRequestFinalizer(&request{task: wkURLSchemeTask})
}
var _ Request = &request{}
@ -135,16 +133,6 @@ type request struct {
rw *responseWriter
}
func (r *request) AddRef() error {
C.URLSchemeTaskRetain(r.task)
return nil
}
func (r *request) Release() error {
C.URLSchemeTaskRelease(r.task)
return nil
}
func (r *request) URL() (string, error) {
return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil
}
@ -205,6 +193,16 @@ func (r *request) Response() ResponseWriter {
return r.rw
}
func (r *request) Close() error {
var err error
if r.body != nil {
err = r.body.Close()
}
r.Response().Finish()
C.URLSchemeTaskRelease(r.task)
return err
}
var _ io.ReadCloser = &requestBodyStreamReader{}
type requestBodyStreamReader struct {

View File

@ -0,0 +1,40 @@
package webview
import (
"runtime"
"sync/atomic"
)
var _ Request = &requestFinalizer{}
type requestFinalizer struct {
Request
closed int32
}
// newRequestFinalizer returns a request with a runtime finalizer to make sure it will be closed from the finalizer
// if it has not been already closed.
// It also makes sure Close() of the wrapping request is only called once.
func newRequestFinalizer(r Request) Request {
rf := &requestFinalizer{Request: r}
// Make sure to async release since it might block the finalizer goroutine for a longer period
runtime.SetFinalizer(rf, func(obj *requestFinalizer) { rf.close(true) })
return rf
}
func (r *requestFinalizer) Close() error {
return r.close(false)
}
func (r *requestFinalizer) close(asyncRelease bool) error {
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
runtime.SetFinalizer(r, nil)
if asyncRelease {
go r.Request.Close()
return nil
} else {
return r.Request.Close()
}
}
return nil
}

View File

@ -18,13 +18,12 @@ import (
)
// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest`
//
// Please make sure to call Release() when finished using the request.
func NewRequest(webKitURISchemeRequest unsafe.Pointer) Request {
webkitReq := (*C.WebKitURISchemeRequest)(webKitURISchemeRequest)
C.g_object_ref(C.gpointer(webkitReq))
req := &request{req: webkitReq}
req.AddRef()
return req
return newRequestFinalizer(req)
}
var _ Request = &request{}
@ -37,16 +36,6 @@ type request struct {
rw *responseWriter
}
func (r *request) AddRef() error {
C.g_object_ref(C.gpointer(r.req))
return nil
}
func (r *request) Release() error {
C.g_object_unref(C.gpointer(r.req))
return nil
}
func (r *request) URL() (string, error) {
return C.GoString(C.webkit_uri_scheme_request_get_uri(r.req)), nil
}
@ -82,3 +71,13 @@ func (r *request) Response() ResponseWriter {
r.rw = &responseWriter{req: r.req}
return r.rw
}
func (r *request) Close() error {
var err error
if r.body != nil {
err = r.body.Close()
}
r.Response().Finish()
C.g_object_unref(C.gpointer(r.req))
return err
}

View File

@ -20,6 +20,6 @@ var (
type ResponseWriter interface {
http.ResponseWriter
// Finish the response and flush all data.
Finish() error
// Finish the response and flush all data. A Finish after the request has already been finished has no effect.
Finish()
}

View File

@ -133,16 +133,15 @@ func (rw *responseWriter) WriteHeader(code int) {
C.URLSchemeTaskDidReceiveResponse(rw.r.task, C.int(code), headers, C.int(headersLen))
}
func (rw *responseWriter) Finish() error {
func (rw *responseWriter) Finish() {
if !rw.wroteHeader {
rw.WriteHeader(http.StatusNotImplemented)
}
if rw.finished {
return nil
return
}
rw.finished = true
C.URLSchemeTaskDidFinish(rw.r.task)
return nil
}

View File

@ -84,19 +84,18 @@ func (rw *responseWriter) WriteHeader(code int) {
}
}
func (rw *responseWriter) Finish() error {
func (rw *responseWriter) Finish() {
if !rw.wroteHeader {
rw.WriteHeader(http.StatusNotImplemented)
}
if rw.finished {
return nil
return
}
rw.finished = true
if rw.w != nil {
rw.w.Close()
}
return nil
}
func (rw *responseWriter) finishWithError(code int, err error) {

View File

@ -8,8 +8,9 @@ type Role int
// These constants need to be kept in sync with `v2/internal/frontend/desktop/darwin/Role.h`
const (
AppMenuRole Role = 1
EditMenuRole = 2
AppMenuRole Role = 1
EditMenuRole = 2
WindowMenuRole = 3
//AboutRole Role = "about"
//UndoRole Role = "undo"
//RedoRole Role = "redo"
@ -142,14 +143,16 @@ func ViewMenu() *MenuItem {
Role: ViewMenuRole,
}
}
*/
// WindowMenu provides a MenuItem with the whole default "Window" menu (Minimize, Zoom, etc.).
// On MacOS currently all options in there won't work if the window is frameless.
func WindowMenu() *MenuItem {
return &MenuItem{
Role: WindowMenuRole,
}
}
*/
// These roles are Mac only
// AppMenu provides a MenuItem with the whole default "App" menu (About, Services, etc.)

View File

@ -160,10 +160,14 @@ func processMenus(appoptions *App) {
switch runtime.GOOS {
case "darwin":
if appoptions.Menu == nil {
appoptions.Menu = menu.NewMenuFromItems(
menu.AppMenu(),
items := []*menu.MenuItem{
menu.EditMenu(),
)
}
if !appoptions.Frameless {
items = append(items, menu.WindowMenu()) // Current options in Window Menu only work if not frameless
}
appoptions.Menu = menu.NewMenuFromItems(menu.AppMenu(), items...)
}
}
}

View File

@ -17,6 +17,6 @@
"svelte-preprocess": "^4.10.7",
"tslib": "^2.4.0",
"typescript": "^4.6.4",
"vite": "^3.0.0"
"vite": "^3.0.7"
}
}

View File

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

View File

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

View File

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

View File

@ -8,72 +8,71 @@ Application interface methods
| Method | Windows | Linux | Mac | Notes |
|---------------------------------------------------------------|---------|-------|-----|-------|
| run() error | | | | |
| destroy() | | | | |
| setApplicationMenu(menu *Menu) | | | | |
| name() string | | | | |
| getCurrentWindowID() uint | | | | |
| showAboutDialog(name string, description string, icon []byte) | | | | |
| setIcon(icon []byte) | | | | |
| on(id uint) | | | | |
| dispatchOnMainThread(id uint) | | | ✅ | |
| hide() | | | ✅ | |
| show() | | | ✅ | |
| getPrimaryScreen() (*Screen, error) | | | | |
| getScreens() ([]*Screen, error) | | | | |
| run() error | | | Y | |
| destroy() | | | Y | |
| setApplicationMenu(menu *Menu) | | | Y | |
| name() string | | | Y | |
| getCurrentWindowID() uint | | | Y | |
| showAboutDialog(name string, description string, icon []byte) | | | Y | |
| setIcon(icon []byte) | | | Y | |
| on(id uint) | | | Y | |
| dispatchOnMainThread(fn func()) | Y | | Y | |
| hide() | Y | | Y | |
| show() | Y | | Y | |
| getPrimaryScreen() (*Screen, error) | | | Y | |
| getScreens() ([]*Screen, error) | | | Y | |
## Webview Window
Webview Window Interface Methods
| Method | Windows | Linux | Mac | Notes |
|----------------------------------------------------|---------|-------|-----|-------|
| setTitle(title string) | | | ✅ | |
| setSize(width, height int) | | | ✅ | |
| setAlwaysOnTop(alwaysOnTop bool) | | | ✅ | |
| setURL(url string) | | | ✅ | |
| setResizable(resizable bool) | | | ✅ | |
| setMinSize(width, height int) | | | ✅ | |
| setMaxSize(width, height int) | | | ✅ | |
| execJS(js string) | | | ✅ | |
| restore() | | | ✅ | |
| setBackgroundColour(color *RGBA) | | | ✅ | |
| run() | | | ✅ | |
| center() | | | ✅ | |
| size() (int, int) | | | ✅ | |
| width() int | | | ✅ | |
| height() int | | | ✅ | |
| position() (int, int) | | | ✅ | |
| destroy() | | | ✅ | |
| reload() | | | ✅ | |
| forceReload() | | | ✅ | |
| toggleDevTools() | | | ✅ | |
| zoomReset() | | | ✅ | |
| zoomIn() | | | ✅ | |
| zoomOut() | | | ✅ | |
| getZoom() float64 | | | ✅ | |
| setZoom(zoom float64) | | | ✅ | |
| close() | | | ✅ | |
| zoom() | | | ✅ | |
| setHTML(html string) | | | ✅ | |
| setPosition(x int, y int) | | | ✅ | |
| on(eventID uint) | | | ✅ | |
| minimise() | | | ✅ | |
| unminimise() | | | ✅ | |
| maximise() | | | ✅ | |
| unmaximise() | | | ✅ | |
| fullscreen() | | | ✅ | |
| unfullscreen() | | | ✅ | |
| isMinimised() bool | | | ✅ | |
| isMaximised() bool | | | ✅ | |
| isFullscreen() bool | | | ✅ | |
| disableSizeConstraints() | | | ✅ | |
| setFullscreenButtonEnabled(enabled bool) | | | ✅ | |
| show() | | | ✅ | |
| hide() | | | ✅ | |
| getScreen() (*Screen, error) | | | ✅ | |
| setFrameless(bool) | | | ✅ | |
| openContextMenu(menu *Menu, data *ContextMenuData) | | | ✅ | |
| Method | Windows | Linux | Mac | Notes |
|----------------------------------------------------|---------|-------|-----|------------------------------------------|
| center() | Y | | Y | |
| close() | | | Y | |
| destroy() | | | Y | |
| execJS(js string) | | | Y | |
| forceReload() | | | Y | |
| fullscreen() | Y | | Y | |
| getScreen() (*Screen, error) | | | Y | |
| getZoom() float64 | | | Y | |
| height() int | Y | | Y | |
| hide() | Y | | Y | |
| isFullscreen() bool | Y | | Y | |
| isMaximised() bool | Y | | Y | |
| isMinimised() bool | Y | | Y | |
| maximise() | Y | | Y | |
| minimise() | Y | | Y | |
| nativeWindowHandle() (uintptr, error) | Y | | | |
| on(eventID uint) | | | Y | |
| openContextMenu(menu *Menu, data *ContextMenuData) | | | Y | |
| position() (int, int) | Y | | Y | |
| reload() | | | Y | |
| run() | Y | | Y | |
| setAlwaysOnTop(alwaysOnTop bool) | Y | | Y | |
| setBackgroundColour(color RGBA) | Y | | Y | |
| setFrameless(bool) | | | Y | |
| setFullscreenButtonEnabled(enabled bool) | - | | Y | There is no fullscreen button in Windows |
| setHTML(html string) | | | Y | |
| setMaxSize(width, height int) | Y | | Y | |
| setMinSize(width, height int) | Y | | Y | |
| setPosition(x int, y int) | Y | | Y | |
| setResizable(resizable bool) | Y | | Y | |
| setSize(width, height int) | Y | | Y | |
| setTitle(title string) | Y | | Y | |
| setURL(url string) | | | Y | |
| setZoom(zoom float64) | | | Y | |
| show() | Y | | Y | |
| size() (int, int) | Y | | Y | |
| toggleDevTools() | | | Y | |
| unfullscreen() | Y | | Y | |
| unmaximise() | Y | | Y | |
| unminimise() | Y | | Y | |
| width() int | Y | | Y | |
| zoom() | | | Y | |
| zoomIn() | | | Y | |
| zoomOut() | | | Y | |
| zoomReset() | | | Y | |
## Runtime
@ -81,73 +80,111 @@ Webview Window Interface Methods
| Feature | Windows | Linux | Mac | Notes |
|---------|---------|-------|-----|-------|
| Quit | | | | |
| Hide | | | ✅ | |
| Show | | | ✅ | |
| Quit | | | Y | |
| Hide | Y | | Y | |
| Show | Y | | Y | |
### Dialogs
| Feature | Windows | Linux | Mac | Notes |
|----------|---------|-------|-----|-------|
| Info | | | | |
| Warning | | | | |
| Error | | | | |
| Question | | | | |
| OpenFile | | | | |
| SaveFile | | | | |
| Info | | | Y | |
| Warning | | | Y | |
| Error | | | Y | |
| Question | | | Y | |
| OpenFile | | | Y | |
| SaveFile | | | Y | |
### Clipboard
| Feature | Windows | Linux | Mac | Notes |
|---------|---------|-------|-----|-------|
| SetText | | | | |
| Text | | | | |
| SetText | | | Y | |
| Text | | | Y | |
### ContextMenu
| Feature | Windows | Linux | Mac | Notes |
|-----------------|---------|-------|-----|-------|
| OpenContextMenu | | | | |
| OpenContextMenu | | | Y | |
### Screens
| Feature | Windows | Linux | Mac | Notes |
|------------|---------|-------|-----|-------|
| GetAll | | | | |
| GetPrimary | | | | |
| GetCurrent | | | | |
| GetAll | | | Y | |
| GetPrimary | | | Y | |
| GetCurrent | | | Y | |
### Window
| Feature | Windows | Linux | Mac | Notes |
|---------------------|---------|-------|-----|--------------------------------------------------------------------------------------|
| SetTitle | | | ✅ | |
| SetSize | | | ✅ | |
| Size | | | ✅ | |
| SetPosition | | | ✅ | |
| Position | | | ✅ | |
| FullScreen | | | ✅ | |
| UnFullscreen | | | ✅ | |
| Minimise | | | ✅ | |
| UnMinimise | | | ✅ | |
| Maximise | | | ✅ | |
| UnMaximise | | | ✅ | |
| Show | | | ✅ | |
| Hide | | | ✅ | |
| Center | | | ✅ | |
| SetBackgroundColour | | | ✅ | https://github.com/MicrosoftEdge/WebView2Feedback/issues/1621#issuecomment-938234294 |
| SetAlwaysOnTop | | | ✅ | |
| SetResizable | | | ✅ | |
| SetMinSize | | | ✅ | |
| SetMaxSize | | | ✅ | |
| Width | | | ✅ | |
| Height | | | ✅ | |
| ZoomIn | | | ✅ | Increase view scale |
| ZoomOut | | | ✅ | Decrease view scale |
| ZoomReset | | | ✅ | Reset view scale |
| GetZoom | | | ✅ | Get current view scale |
| SetZoom | | | ✅ | Set view scale |
| Screen | | | ✅ | Get screen for window |
| SetTitle | | | Y | |
| SetSize | | | Y | |
| Size | | | Y | |
| SetPosition | | | Y | |
| Position | | | Y | |
| FullScreen | | | Y | |
| UnFullscreen | | | Y | |
| Minimise | | | Y | |
| UnMinimise | | | Y | |
| Maximise | | | Y | |
| UnMaximise | | | Y | |
| Show | | | Y | |
| Hide | | | Y | |
| Center | | | Y | |
| SetBackgroundColour | | | Y | https://github.com/MicrosoftEdge/WebView2Feedback/issues/1621#issuecomment-938234294 |
| SetAlwaysOnTop | | | Y | |
| SetResizable | | | Y | |
| SetMinSize | | | Y | |
| SetMaxSize | | | Y | |
| Width | | | Y | |
| Height | | | Y | |
| ZoomIn | | | Y | Increase view scale |
| ZoomOut | | | Y | Decrease view scale |
| ZoomReset | | | Y | Reset view scale |
| GetZoom | | | Y | Get current view scale |
| SetZoom | | | Y | Set view scale |
| Screen | | | Y | Get screen for window |
### Window Options
A 'Y' in the table below indicates that the option has been tested and is applied when the window is created.
An 'X' indicates that the option is not supported by the platform.
| Feature | Windows | Linux | Mac | Notes |
|---------------------------------|---------|-------|-----|--------------------------------------------|
| Name | | | | |
| Title | Y | | | |
| Width | Y | | | |
| Height | Y | | | |
| AlwaysOnTop | Y | | | |
| URL | | | | |
| DisableResize | Y | | | |
| Frameless | | | | |
| MinWidth | Y | | | |
| MinHeight | Y | | | |
| MaxWidth | Y | | | |
| MaxHeight | Y | | | |
| StartState | Y | | | |
| Mac | - | - | | |
| BackgroundType | | | | Acrylic seems to work but the others don't |
| BackgroundColour | Y | | | |
| HTML | | | | |
| JS | | | | |
| CSS | | | | |
| X | | | | |
| Y | | | | |
| HideOnClose | | | | |
| FullscreenButtonEnabled | | | | |
| Hidden | | | | |
| EnableFraudulentWebsiteWarnings | | | | |
| Zoom | | | | |
| EnableDragAndDrop | | | | |
| Windows | | - | - | |
### Log
@ -157,16 +194,16 @@ To log or not to log? System logger vs custom logger.
| Event | Windows | Linux | Mac | Notes |
|--------------------------|---------|-------|-----|-------|
| Default Application Menu | | | | |
| Default Application Menu | | | Y | |
## Tray Menus
| Feature | Windows | Linux | Mac | Notes |
|--------------------|---------|-------|-----|-------|
| Icon | | | | |
| Label | | | | |
| Icon | | | Y | |
| Label | | | Y | |
| Label (ANSI Codes) | | | | |
| Menu | | | | |
| Menu | | | Y | |
## Cross Platform Events
@ -196,11 +233,11 @@ TBD
## Theme
| Plugin | Windows | Linux | Mac | Notes |
|-----------------|---------|-------|-----|-------|
| Dark | | | | |
| Light | | | | |
| System | | | | |
| Plugin | Windows | Linux | Mac | Notes |
|--------|---------|-------|-----|-------|
| Dark | Y | | | |
| Light | Y | | | |
| System | Y | | | |
## NSIS Installer
@ -216,31 +253,31 @@ Built-in plugin support:
| Plugin | Windows | Linux | Mac | Notes |
|-----------------|---------|-------|-----|-------|
| Browser | | | | |
| KV Store | | | | |
| Log | | | | |
| Single Instance | | | | |
| SQLite | | | | |
| Start at login | | | | |
| Browser | | | Y | |
| KV Store | | | Y | |
| Log | | | Y | |
| Single Instance | | | Y | |
| SQLite | | | Y | |
| Start at login | | | Y | |
| Server | | | | |
## Packaging
| | Windows | Linux | Mac | Notes |
|-----------------|---------|-------|-----|-------|
| Icon Generation | | | | |
| Icon Embedding | | | | |
| Info.plist | | | | |
| NSIS Installer | | | | |
| Mac bundle | | | | |
| Windows exe | | | | |
| Icon Generation | | | Y | |
| Icon Embedding | | | Y | |
| Info.plist | | | Y | |
| NSIS Installer | | | - | |
| Mac bundle | | | Y | |
| Windows exe | | | - | |
## Frameless Windows
| Feature | Windows | Linux | Mac | Notes |
|---------|---------|-------|----|-------|
| Resize | | | | |
| Drag | | | | |
|---------|---------|-------|-----|-------|
| Resize | | | | |
| Drag | | | | |
## Mac Specific
@ -248,4 +285,7 @@ Built-in plugin support:
## Windows Specific
- [x] Translucency
- [x] Custom Themes
## Linux Specific

View File

@ -180,3 +180,39 @@ const MyEnum = {
- Why use `float64`? Can't we use `int`?
- Because JavaScript doesn't have a concept of `int`. Everything is a `number`, which translates to `float64` in Go. There are also restrictions on casting types in Go's reflection package, which means using `int` doesn't work.
### BackgroundColour
In v2, this was a pointer to an `RGBA` struct. In v3, this is an `RGBA` struct value.
### WindowIsTranslucent
This flag has been removed. Now there is a `BackgroundType` flag that can be used to set the type of background the window should have.
This flag can be set to any of the following values:
- `BackgroundTypeSolid` - The window will have a solid background
- `BackgroundTypeTransparent` - The window will have a transparent background
- `BackgroundTypeTranslucent` - The window will have a translucent background
On Windows, if the `BackgroundType` is set to `BackgroundTypeTranslucent`, the type of translucency can be set using the
`BackdropType` flag in the `WindowsWindow` options. This can be set to any of the following values:
- `Auto` - The window will use an effect determined by the system
- `None` - The window will have no background
- `Mica` - The window will use the Mica effect
- `Acrylic` - The window will use the acrylic effect
- `Tabbed` - The window will use the tabbed effect
## Windows Application Options
### WndProcInterceptor
If this is set, the WndProc will be intercepted and the function will be called. This allows you to handle Windows
messages directly. The function should have the following signature:
```go
func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnValue uintptr, shouldReturn)
```
The `shouldReturn` value should be set to `true` if the returnValue should be returned by the main wndProc method.
If it is set to `false`, the return value will be ignored and the message will continue to be processed by the main
wndProc method.

View File

@ -14,6 +14,7 @@ require (
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.7.0 // indirect
)
replace github.com/wailsapp/wails/v3 => ../..

View File

@ -27,6 +27,8 @@ golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -19,14 +19,15 @@ func main() {
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
})
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
Assets: application.AssetOptions{
FS: assets,
},
})
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
URL: "/",
})
err := app.Run()
if err != nil {

View File

@ -67,7 +67,7 @@ func main() {
if runtime.GOOS == "darwin" {
myMenu.Add("New WebviewWindow (MacTitleBarHiddenInset)").
OnClick(func(ctx *application.Context) {
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Mac: application.MacWindow{
TitleBar: application.MacTitleBarHiddenInset,
InvisibleTitleBarHeight: 25,
@ -81,7 +81,7 @@ func main() {
})
myMenu.Add("New WebviewWindow (MacTitleBarHiddenInsetUnified)").
OnClick(func(ctx *application.Context) {
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Mac: application.MacWindow{
TitleBar: application.MacTitleBarHiddenInsetUnified,
InvisibleTitleBarHeight: 50,
@ -95,7 +95,7 @@ func main() {
})
myMenu.Add("New WebviewWindow (MacTitleBarHidden)").
OnClick(func(ctx *application.Context) {
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Mac: application.MacWindow{
TitleBar: application.MacTitleBarHidden,
InvisibleTitleBarHeight: 25,

View File

@ -25,7 +25,7 @@ func main() {
},
})
mainWindow := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Context Menu Demo",
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,
@ -34,7 +34,7 @@ func main() {
},
})
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Context Menu Demo",
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,

View File

@ -25,7 +25,7 @@ func main() {
},
})
window := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Drag-n-drop Demo",
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,

View File

@ -41,7 +41,7 @@ func main() {
}
})
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Events Demo",
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,
@ -49,7 +49,7 @@ func main() {
InvisibleTitleBarHeight: 50,
},
})
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Events Demo",
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,

View File

@ -109,13 +109,13 @@ func main() {
mySystray.SetMenu(myMenu)
mySystray.SetIconPosition(application.NSImageLeading)
myWindow := app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
myWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Kitchen Sink",
Width: 600,
Height: 400,
AlwaysOnTop: true,
DisableResize: false,
BackgroundColour: &application.RGBA{
BackgroundColour: application.RGBA{
Red: 255,
Green: 255,
Blue: 255,
@ -184,7 +184,7 @@ func main() {
*/
var myWindow2 *application.WebviewWindow
var myWindow2Lock sync.RWMutex
myWindow2 = app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
myWindow2 = app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "#2",
Width: 1024,
Height: 768,

View File

@ -4,6 +4,7 @@ import (
_ "embed"
"log"
"net/http"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
)
@ -23,7 +24,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; user-select: none; -ms-user-select: none; -webkit-user-select: none; } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{
@ -38,6 +39,21 @@ func main() {
println("clicked")
})
go func() {
time.Sleep(5 * time.Second)
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle new Window from GoRoutine",
Width: 500,
Height: 500,
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,
TitleBar: application.MacTitleBarHiddenInsetUnified,
InvisibleTitleBarHeight: 50,
},
})
}()
err := app.Run()
if err != nil {

View File

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

View File

@ -56,7 +56,7 @@ func main() {
myMenu.Add("New WebviewWindow (Hide on Close").
SetAccelerator("CmdOrCtrl+H").
OnClick(func(ctx *application.Context) {
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{HideOnClose: true}).
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{HideOnClose: true}).
SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)).
SetPosition(rand.Intn(1000), rand.Intn(800)).
SetURL("https://wails.io").
@ -66,7 +66,7 @@ func main() {
myMenu.Add("New Frameless WebviewWindow").
SetAccelerator("CmdOrCtrl+F").
OnClick(func(ctx *application.Context) {
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
X: rand.Intn(1000),
Y: rand.Intn(800),
Frameless: true,
@ -79,7 +79,7 @@ func main() {
if runtime.GOOS == "darwin" {
myMenu.Add("New WebviewWindow (MacTitleBarHiddenInset)").
OnClick(func(ctx *application.Context) {
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Mac: application.MacWindow{
TitleBar: application.MacTitleBarHiddenInset,
InvisibleTitleBarHeight: 25,
@ -93,7 +93,7 @@ func main() {
})
myMenu.Add("New WebviewWindow (MacTitleBarHiddenInsetUnified)").
OnClick(func(ctx *application.Context) {
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Mac: application.MacWindow{
TitleBar: application.MacTitleBarHiddenInsetUnified,
InvisibleTitleBarHeight: 50,
@ -107,7 +107,7 @@ func main() {
})
myMenu.Add("New WebviewWindow (MacTitleBarHidden)").
OnClick(func(ctx *application.Context) {
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Mac: application.MacWindow{
TitleBar: application.MacTitleBarHidden,
InvisibleTitleBarHeight: 25,

View File

@ -34,7 +34,7 @@ func main() {
newWindow := func() {
windowName := "WebviewWindow " + strconv.Itoa(windowCounter)
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Name: windowName,
}).
SetTitle(windowName).

View File

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

View File

@ -17,6 +17,7 @@ require (
github.com/samber/lo v1.37.0
github.com/tc-hib/winres v0.1.6
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6
golang.org/x/sys v0.7.0
modernc.org/sqlite v1.21.0
)
@ -53,7 +54,6 @@ require (
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.1.12 // indirect

View File

@ -182,8 +182,8 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -20,7 +20,7 @@ func main() {
},
})
// Create window
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Plain Bundle",
CSS: `body { background-color: rgba(255, 255, 255, 0); } .main { color: white; margin: 20%; }`,
Mac: application.MacWindow{

View File

@ -8,6 +8,8 @@ import (
"strconv"
"sync"
"github.com/samber/lo"
"github.com/wailsapp/wails/v2/pkg/assetserver"
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
assetserveroptions "github.com/wailsapp/wails/v2/pkg/options/assetserver"
@ -23,6 +25,10 @@ func init() {
runtime.LockOSThread()
}
type EventListener struct {
callback func()
}
func New(appOptions Options) *App {
if globalApplication != nil {
return globalApplication
@ -32,7 +38,7 @@ func New(appOptions Options) *App {
result := &App{
options: appOptions,
applicationEventListeners: make(map[uint][]func()),
applicationEventListeners: make(map[uint][]*EventListener),
systemTrays: make(map[uint]*SystemTray),
log: logger.New(appOptions.Logger.CustomLoggers...),
contextMenus: make(map[string]*Menu),
@ -155,7 +161,7 @@ var webviewRequests = make(chan *webViewAssetRequest)
type App struct {
options Options
applicationEventListeners map[uint][]func()
applicationEventListeners map[uint][]*EventListener
applicationEventListenersLock sync.RWMutex
// Windows
@ -216,17 +222,28 @@ func (a *App) deleteWindowByID(id uint) {
delete(a.windows, id)
}
func (a *App) On(eventType events.ApplicationEventType, callback func()) {
func (a *App) On(eventType events.ApplicationEventType, callback func()) func() {
eventID := uint(eventType)
a.applicationEventListenersLock.Lock()
defer a.applicationEventListenersLock.Unlock()
a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], callback)
listener := &EventListener{
callback: callback,
}
a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], listener)
if a.impl != nil {
go a.impl.on(eventID)
}
return func() {
// lock the map
a.applicationEventListenersLock.Lock()
defer a.applicationEventListenersLock.Unlock()
// Remove listener
a.applicationEventListeners[eventID] = lo.Without(a.applicationEventListeners[eventID], listener)
}
}
func (a *App) NewWebviewWindow() *WebviewWindow {
return a.NewWebviewWindowWithOptions(&WebviewWindowOptions{})
return a.NewWebviewWindowWithOptions(WebviewWindowOptions{})
}
func (a *App) GetPID() int {
@ -267,11 +284,7 @@ func (a *App) error(message string, args ...any) {
})
}
func (a *App) NewWebviewWindowWithOptions(windowOptions *WebviewWindowOptions) *WebviewWindow {
// Ensure we have sane defaults
if windowOptions == nil {
windowOptions = WebviewWindowDefaults
}
func (a *App) NewWebviewWindowWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow {
newWindow := NewWindow(windowOptions)
id := newWindow.id
if a.windows == nil {
@ -327,10 +340,6 @@ func (a *App) Run() error {
for {
request := <-webviewRequests
a.handleWebViewRequest(request)
err := request.Release()
if err != nil {
a.error("Failed to release webview request: %s", err.Error())
}
}
}()
go func() {
@ -364,10 +373,10 @@ func (a *App) Run() error {
}
// set the application menu
a.impl.setApplicationMenu(a.ApplicationMenu)
// set the application Icon
a.impl.setIcon(a.options.Icon)
if runtime.GOOS == "darwin" {
a.impl.setApplicationMenu(a.ApplicationMenu)
a.impl.setIcon(a.options.Icon)
}
err := a.impl.run()
if err != nil {
@ -387,7 +396,7 @@ func (a *App) handleApplicationEvent(event uint) {
return
}
for _, listener := range listeners {
go listener()
go listener.callback()
}
}
@ -606,3 +615,35 @@ func (a *App) GetWindowByName(name string) *WebviewWindow {
}
return nil
}
func invokeSync(fn func()) {
var wg sync.WaitGroup
wg.Add(1)
globalApplication.dispatchOnMainThread(func() {
fn()
wg.Done()
})
wg.Wait()
}
func invokeSyncWithResult[T any](fn func() T) (res T) {
var wg sync.WaitGroup
wg.Add(1)
globalApplication.dispatchOnMainThread(func() {
res = fn()
wg.Done()
})
wg.Wait()
return res
}
func invokeSyncWithResultAndError[T any](fn func() (T, error)) (res T, err error) {
var wg sync.WaitGroup
wg.Add(1)
globalApplication.dispatchOnMainThread(func() {
res, err = fn()
wg.Done()
})
wg.Wait()
return res, err
}

View File

@ -2,14 +2,35 @@
package application
type windowsApp struct {
//applicationMenu unsafe.Pointer
parent *App
}
import (
"os"
"syscall"
"unsafe"
func (m *windowsApp) dispatchOnMainThread(id uint) {
//TODO implement me
panic("implement me")
"github.com/wailsapp/wails/v3/pkg/events"
"github.com/wailsapp/wails/v3/pkg/w32"
"github.com/samber/lo"
)
var windowClassName = lo.Must(syscall.UTF16PtrFromString("WailsWebviewWindow"))
type windowsApp struct {
parent *App
instance w32.HINSTANCE
windowMap map[w32.HWND]*windowsWebviewWindow
mainThreadID w32.HANDLE
mainThreadWindowHWND w32.HWND
// Windows hidden by application.Hide()
hiddenWindows []*windowsWebviewWindow
focusedWindow w32.HWND
// system theme
isDarkMode bool
}
func (m *windowsApp) getPrimaryScreen() (*Screen, error) {
@ -23,11 +44,29 @@ func (m *windowsApp) getScreens() ([]*Screen, error) {
}
func (m *windowsApp) hide() {
//C.hide()
// Get the current focussed window
m.focusedWindow = w32.GetForegroundWindow()
// Iterate over all windows and hide them if they aren't already hidden
for _, window := range m.windowMap {
if window.isVisible() {
// Add to hidden windows
m.hiddenWindows = append(m.hiddenWindows, window)
window.hide()
}
}
// Switch focus to the next application
hwndNext := w32.GetWindow(m.mainThreadWindowHWND, w32.GW_HWNDNEXT)
w32.SetForegroundWindow(hwndNext)
}
func (m *windowsApp) show() {
//C.show()
// Iterate over all windows and show them if they were previously hidden
for _, window := range m.hiddenWindows {
window.show()
}
// Show the foreground window
w32.SetForegroundWindow(m.focusedWindow)
}
func (m *windowsApp) on(eventID uint) {
@ -73,6 +112,9 @@ func (m *windowsApp) run() error {
for eventID := range m.parent.applicationEventListeners {
m.on(eventID)
}
_ = m.runMainLoop()
//C.run()
return nil
}
@ -81,9 +123,110 @@ func (m *windowsApp) destroy() {
//C.destroyApp()
}
func newPlatformApp(app *App) *windowsApp {
//C.init()
return &windowsApp{
parent: app,
func (m *windowsApp) init() {
// Register the window class
icon := w32.LoadIconWithResourceID(m.instance, w32.IDI_APPLICATION)
var wc w32.WNDCLASSEX
wc.Size = uint32(unsafe.Sizeof(wc))
wc.Style = w32.CS_HREDRAW | w32.CS_VREDRAW
wc.WndProc = syscall.NewCallback(m.wndProc)
wc.Instance = m.instance
wc.Background = w32.COLOR_BTNFACE + 1
wc.Icon = icon
wc.Cursor = w32.LoadCursorWithResourceID(0, w32.IDC_ARROW)
wc.ClassName = windowClassName
wc.MenuName = nil
wc.IconSm = icon
if ret := w32.RegisterClassEx(&wc); ret == 0 {
panic(syscall.GetLastError())
}
m.isDarkMode = w32.IsCurrentlyDarkMode()
}
func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr {
// Handle the invoke callback
if msg == wmInvokeCallback {
m.invokeCallback(wParam, lParam)
return 0
}
// If the WndProcInterceptor is set in options, pass the message on
if m.parent.options.Windows.WndProcInterceptor != nil {
returnValue, shouldReturn := m.parent.options.Windows.WndProcInterceptor(hwnd, msg, wParam, lParam)
if shouldReturn {
return returnValue
}
}
switch msg {
case w32.WM_SETTINGCHANGE:
settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam)))
if settingChanged == "ImmersiveColorSet" {
isDarkMode := w32.IsCurrentlyDarkMode()
if isDarkMode != m.isDarkMode {
applicationEvents <- uint(events.Windows.SystemThemeChanged)
m.isDarkMode = isDarkMode
}
}
return 0
case w32.WM_POWERBROADCAST:
switch wParam {
case w32.PBT_APMPOWERSTATUSCHANGE:
applicationEvents <- uint(events.Windows.APMPowerStatusChange)
case w32.PBT_APMSUSPEND:
applicationEvents <- uint(events.Windows.APMSuspend)
case w32.PBT_APMRESUMEAUTOMATIC:
applicationEvents <- uint(events.Windows.APMResumeAutomatic)
case w32.PBT_APMRESUMESUSPEND:
applicationEvents <- uint(events.Windows.APMResumeSuspend)
case w32.PBT_POWERSETTINGCHANGE:
applicationEvents <- uint(events.Windows.APMPowerSettingChange)
}
return 0
}
if window, ok := m.windowMap[hwnd]; ok {
return window.WndProc(msg, wParam, lParam)
}
// Dispatch the message to the appropriate window
return w32.DefWindowProc(hwnd, msg, wParam, lParam)
}
func (m *windowsApp) registerWindow(result *windowsWebviewWindow) {
m.windowMap[result.hwnd] = result
}
func (m *windowsApp) unregisterWindow(w *windowsWebviewWindow) {
delete(m.windowMap, w.hwnd)
// If this was the last window...
if len(m.windowMap) == 0 {
w32.PostQuitMessage(0)
}
}
func newPlatformApp(app *App) *windowsApp {
err := w32.SetProcessDPIAware()
if err != nil {
println("Fatal error in application initialisation: ", err.Error())
os.Exit(1)
}
result := &windowsApp{
parent: app,
instance: w32.GetModuleHandle(""),
windowMap: make(map[w32.HWND]*windowsWebviewWindow),
}
result.init()
result.initMainLoop()
return result
}

View File

@ -0,0 +1,126 @@
//go:build windows
package application
import (
"github.com/wailsapp/wails/v3/pkg/w32"
"runtime"
"sort"
"unsafe"
)
var (
wmInvokeCallback uint32
)
func init() {
wmInvokeCallback = w32.RegisterWindowMessage(w32.MustStringToUTF16Ptr("WailsV0.InvokeCallback"))
}
// initMainLoop must be called with the same OSThread that is used to call runMainLoop() later.
func (m *windowsApp) initMainLoop() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if m.mainThreadWindowHWND != 0 {
panic("initMainLoop was already called")
}
// We need a hidden window so we can PostMessage to it, if we don't use PostMessage for dispatching to a HWND
// messages might get lost if a modal inner loop is being run.
// We had this once in V2: https://github.com/wailsapp/wails/issues/969
// See: https://devblogs.microsoft.com/oldnewthing/20050426-18/?p=35783
// See also: https://learn.microsoft.com/en-us/windows/win32/winmsg/using-messages-and-message-queues#creating-a-message-loop
// > Because the system directs messages to individual windows in an application, a thread must create at least one window before starting its message loop.
m.mainThreadWindowHWND = w32.CreateWindowEx(
0,
windowClassName,
w32.MustStringToUTF16Ptr("__wails_hidden_mainthread"),
w32.WS_DISABLED,
w32.CW_USEDEFAULT,
w32.CW_USEDEFAULT,
0,
0,
0,
0,
w32.GetModuleHandle(""),
nil)
m.mainThreadID, _ = w32.GetWindowThreadProcessId(m.mainThreadWindowHWND)
}
func (m *windowsApp) runMainLoop() int {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if m.invokeRequired() {
panic("invokeRequired for runMainLoop, the mainloop must be running on the same OSThread as the mainThreadWindow has been created on")
}
msg := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{})))))
defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(m)))
for w32.GetMessage(msg, 0, 0, 0) != 0 {
w32.TranslateMessage(msg)
w32.DispatchMessage(msg)
}
return int(msg.WParam)
}
func (m *windowsApp) dispatchOnMainThread(id uint) {
mainThreadHWND := m.mainThreadWindowHWND
if mainThreadHWND == 0 {
panic("initMainLoop was not called")
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if m.invokeRequired() {
w32.PostMessage(mainThreadHWND, wmInvokeCallback, uintptr(id), 0)
} else {
mainThreadFunctionStoreLock.Lock()
fn := mainThreadFunctionStore[id]
delete(mainThreadFunctionStore, id)
mainThreadFunctionStoreLock.Unlock()
if fn == nil {
Fatal("dispatchOnMainThread called with invalid id: %v", id)
}
fn()
}
}
func (m *windowsApp) invokeRequired() bool {
mainThreadID := m.mainThreadID
if mainThreadID == 0 {
panic("initMainLoop was not called")
}
return mainThreadID != w32.GetCurrentThreadId()
}
func (m *windowsApp) invokeCallback(wParam, lParam uintptr) {
// TODO: Should we invoke just one or all queued? In v2 we always invoked all pendings...
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if m.invokeRequired() {
panic("invokeCallback must always be called on the MainOSThread")
}
mainThreadFunctionStoreLock.Lock()
fnIDs := make([]uint, 0, len(mainThreadFunctionStore))
for id := range mainThreadFunctionStore {
fnIDs = append(fnIDs, id)
}
sort.Slice(fnIDs, func(i, j int) bool { return fnIDs[i] < fnIDs[j] })
fns := make([]func(), len(fnIDs))
for i, id := range fnIDs {
fns[i] = mainThreadFunctionStore[id]
delete(mainThreadFunctionStore, id)
}
mainThreadFunctionStoreLock.Unlock()
for _, fn := range fns {
fn()
}
}

View File

@ -96,14 +96,3 @@ func (m *Menu) setContextData(data *ContextMenuData) {
func (a *App) NewMenu() *Menu {
return &Menu{}
}
func defaultApplicationMenu() *Menu {
menu := NewMenu()
menu.AddRole(AppMenu)
menu.AddRole(FileMenu)
menu.AddRole(EditMenu)
menu.AddRole(ViewMenu)
menu.AddRole(WindowMenu)
menu.AddRole(HelpMenu)
return menu
}

View File

@ -103,3 +103,14 @@ func (m *macosMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
}
}
func defaultApplicationMenu() *Menu {
menu := NewMenu()
menu.AddRole(AppMenu)
menu.AddRole(FileMenu)
menu.AddRole(EditMenu)
menu.AddRole(ViewMenu)
menu.AddRole(WindowMenu)
menu.AddRole(HelpMenu)
return menu
}

View File

@ -50,3 +50,13 @@ func (m *windowsMenu) processMenu(parent unsafe.Pointer, menu *Menu) {
//
//}
}
func defaultApplicationMenu() *Menu {
menu := NewMenu()
menu.AddRole(FileMenu)
menu.AddRole(EditMenu)
menu.AddRole(ViewMenu)
menu.AddRole(WindowMenu)
menu.AddRole(HelpMenu)
return menu
}

View File

@ -173,6 +173,8 @@ func newRole(role Role) *MenuItem {
return newMinimizeMenuItem()
case Zoom:
return newZoomMenuItem()
case FullScreen:
return newFullScreenMenuItem()
default:
println("No support for role:", role)

View File

@ -606,7 +606,7 @@ func newMinimizeMenuItem() *MenuItem {
OnClick(func(ctx *Context) {
currentWindow := globalApplication.CurrentWindow()
if currentWindow != nil {
currentWindow.Minimize()
currentWindow.Minimise()
}
})
}
@ -620,3 +620,13 @@ func newZoomMenuItem() *MenuItem {
}
})
}
func newFullScreenMenuItem() *MenuItem {
return newMenuItem("Fullscreen").
OnClick(func(ctx *Context) {
currentWindow := globalApplication.CurrentWindow()
if currentWindow != nil {
currentWindow.Fullscreen()
}
})
}

View File

@ -177,3 +177,7 @@ func newMinimizeMenuItem() *MenuItem {
func newZoomMenuItem() *MenuItem {
panic("implement me")
}
func newFullScreenMenuItem() *MenuItem {
panic("implement me")
}

View File

@ -46,7 +46,7 @@ func (m *MessageProcessor) processWindowMethod(method string, rw http.ResponseWr
window.UnFullscreen()
m.ok(rw)
case "Minimise":
window.Minimize()
window.Minimise()
m.ok(rw)
case "UnMinimise":
window.UnMinimise()
@ -102,7 +102,7 @@ func (m *MessageProcessor) processWindowMethod(method string, rw http.ResponseWr
m.Error("Invalid SetBackgroundColour Message: 'a' value required")
return
}
window.SetBackgroundColour(&RGBA{
window.SetBackgroundColour(RGBA{
Red: *r,
Green: *g,
Blue: *b,

View File

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

View File

@ -12,7 +12,8 @@ const (
type WebviewWindowOptions struct {
Name string
Title string
Width, Height int
Width int
Height int
AlwaysOnTop bool
URL string
DisableResize bool
@ -23,7 +24,8 @@ type WebviewWindowOptions struct {
MaxHeight int
StartState WindowState
Mac MacWindow
BackgroundColour *RGBA
BackgroundType BackgroundType
BackgroundColour RGBA
HTML string
JS string
CSS string
@ -35,6 +37,7 @@ type WebviewWindowOptions struct {
EnableFraudulentWebsiteWarnings bool
Zoom float64
EnableDragAndDrop bool
Windows WindowsWindow
}
var WebviewWindowDefaults = &WebviewWindowOptions{
@ -42,8 +45,22 @@ var WebviewWindowDefaults = &WebviewWindowOptions{
Width: 800,
Height: 600,
URL: "",
BackgroundColour: RGBA{
Red: 255,
Green: 255,
Blue: 255,
Alpha: 255,
},
}
type RGBA struct {
Red, Green, Blue, Alpha uint8
}
type BackgroundType int
const (
BackgroundTypeSolid BackgroundType = iota
BackgroundTypeTransparent
BackgroundTypeTranslucent
)

View File

@ -0,0 +1,62 @@
package application
type WindowsApplicationOptions struct {
// WndProcInterceptor is a function that will be called for every message sent in the application.
// Use this to hook into the main message loop. This is useful for handling custom window messages.
// If `shouldReturn` is `true` then `returnCode` will be returned by the main message loop.
// If `shouldReturn` is `false` then returnCode will be ignored and the message will be processed by the main message loop.
WndProcInterceptor func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnCode uintptr, shouldReturn bool)
}
type BackdropType int32
const (
Auto BackdropType = 0
None BackdropType = 1
Mica BackdropType = 2
Acrylic BackdropType = 3
Tabbed BackdropType = 4
)
type WindowsWindow struct {
// Select the type of translucent backdrop. Requires Windows 11 22621 or later.
BackdropType BackdropType
// Disable the icon in the titlebar
DisableIcon bool
// Theme. Defaults to SystemDefault which will use whatever the system theme is. The application will follow system theme changes.
Theme Theme
// Custom colours for dark/light mode
CustomTheme *ThemeSettings
// Disable all window decorations in Frameless mode, which means no "Aero Shadow" and no "Rounded Corner" will be shown.
// "Rounded Corners" are only available on Windows 11.
DisableFramelessWindowDecorations bool
}
type Theme int
const (
// SystemDefault will use whatever the system theme is. The application will follow system theme changes.
SystemDefault Theme = 0
// Dark Mode
Dark Theme = 1
// Light Mode
Light Theme = 2
)
// ThemeSettings defines custom colours to use in dark or light mode.
// They may be set using the hex values: 0x00BBGGRR
type ThemeSettings struct {
DarkModeTitleBar int32
DarkModeTitleBarInactive int32
DarkModeTitleText int32
DarkModeTitleTextInactive int32
DarkModeBorder int32
DarkModeBorderInactive int32
LightModeTitleBar int32
LightModeTitleBarInactive int32
LightModeTitleText int32
LightModeTitleTextInactive int32
LightModeBorder int32
LightModeBorderInactive int32
}

View File

@ -42,8 +42,9 @@ const (
ZoomOut Role = iota
ToggleFullscreen Role = iota
Minimize Role = iota
Zoom Role = iota
Minimize Role = iota
Zoom Role = iota
FullScreen Role = iota
//Front Role = iota
//WindowRole Role = iota
@ -132,6 +133,12 @@ func newWindowMenu() *MenuItem {
menu := NewMenu()
menu.AddRole(Minimize)
menu.AddRole(Zoom)
if runtime.GOOS == "darwin" {
menu.AddSeparator()
menu.AddRole(FullScreen)
} else {
menu.AddRole(Close)
}
subMenu := newSubMenuItem("Window")
subMenu.submenu = menu
return subMenu

View File

@ -128,12 +128,12 @@ func cScreenToScreen(screen C.Screen) *Screen {
}
}
func getPrimaryScreen() (*Screen, error) {
func (m *macosApp) getPrimaryScreen() (*Screen, error) {
cScreen := C.GetPrimaryScreen()
return cScreenToScreen(cScreen), nil
}
func getScreens() ([]*Screen, error) {
func (m *macosApp) getScreens() ([]*Screen, error) {
cScreens := C.getAllScreens()
defer C.free(unsafe.Pointer(cScreens))
numScreens := int(C.GetNumScreens())

View File

@ -1,7 +1,9 @@
package application
import (
"errors"
"fmt"
"github.com/samber/lo"
"sync"
"time"
@ -20,8 +22,7 @@ type (
setMinSize(width, height int)
setMaxSize(width, height int)
execJS(js string)
restore()
setBackgroundColour(color *RGBA)
setBackgroundColour(color RGBA)
run()
center()
size() (int, int)
@ -51,27 +52,37 @@ type (
isMinimised() bool
isMaximised() bool
isFullscreen() bool
disableSizeConstraints()
isNormal() bool
isVisible() bool
setFullscreenButtonEnabled(enabled bool)
show()
hide()
getScreen() (*Screen, error)
setFrameless(bool)
openContextMenu(menu *Menu, data *ContextMenuData)
nativeWindowHandle() uintptr
}
)
type WindowEventListener struct {
callback func(ctx *WindowEventContext)
}
type WebviewWindow struct {
options *WebviewWindowOptions
options WebviewWindowOptions
impl webviewWindowImpl
implLock sync.RWMutex
id uint
eventListeners map[uint][]func(ctx *WindowEventContext)
eventListeners map[uint][]*WindowEventListener
eventListenersLock sync.RWMutex
contextMenus map[string]*Menu
contextMenusLock sync.RWMutex
// A map of listener cancellation functions
cancellersLock sync.RWMutex
cancellers []func()
}
var windowID uint
@ -84,7 +95,15 @@ func getWindowID() uint {
return windowID
}
func NewWindow(options *WebviewWindowOptions) *WebviewWindow {
// Use onApplicationEvent to register a callback for an application event from a window.
// This will handle tidying up the callback when the window is destroyed
func (w *WebviewWindow) onApplicationEvent(eventType events.ApplicationEventType, callback func()) {
cancelFn := globalApplication.On(eventType, callback)
w.addCancellationFunction(cancelFn)
}
// NewWindow creates a new window with the given options
func NewWindow(options WebviewWindowOptions) *WebviewWindow {
if options.Width == 0 {
options.Width = 800
}
@ -98,27 +117,38 @@ func NewWindow(options *WebviewWindowOptions) *WebviewWindow {
result := &WebviewWindow{
id: getWindowID(),
options: options,
eventListeners: make(map[uint][]func(ctx *WindowEventContext)),
eventListeners: make(map[uint][]*WindowEventListener),
contextMenus: make(map[string]*Menu),
}
return result
}
func (w *WebviewWindow) addCancellationFunction(canceller func()) {
w.cancellersLock.Lock()
defer w.cancellersLock.Unlock()
w.cancellers = append(w.cancellers, canceller)
}
// SetTitle sets the title of the window
func (w *WebviewWindow) SetTitle(title string) *WebviewWindow {
w.implLock.RLock()
defer w.implLock.RUnlock()
w.options.Title = title
if w.impl != nil {
w.impl.setTitle(title)
invokeSync(func() {
w.impl.setTitle(title)
})
}
return w
}
// Name returns the name of the window
func (w *WebviewWindow) Name() string {
return w.options.Name
}
// SetSize sets the size of the window
func (w *WebviewWindow) SetSize(width, height int) *WebviewWindow {
// Don't set size if fullscreen
if w.IsFullscreen() {
@ -154,7 +184,9 @@ func (w *WebviewWindow) SetSize(width, height int) *WebviewWindow {
}
if w.impl != nil {
w.impl.setSize(width, height)
invokeSync(func() {
w.impl.setSize(width, height)
})
}
return w
}
@ -166,17 +198,21 @@ func (w *WebviewWindow) run() {
w.implLock.Lock()
w.impl = newWindowImpl(w)
w.implLock.Unlock()
w.impl.run()
invokeSync(w.impl.run)
}
// SetAlwaysOnTop sets the window to be always on top.
func (w *WebviewWindow) SetAlwaysOnTop(b bool) *WebviewWindow {
w.options.AlwaysOnTop = b
if w.impl == nil {
w.impl.setAlwaysOnTop(b)
if w.impl != nil {
invokeSync(func() {
w.impl.setAlwaysOnTop(b)
})
}
return w
}
// Show shows the window.
func (w *WebviewWindow) Show() *WebviewWindow {
if globalApplication.impl == nil {
return w
@ -185,13 +221,15 @@ func (w *WebviewWindow) Show() *WebviewWindow {
w.run()
return w
}
w.impl.show()
invokeSync(w.impl.show)
return w
}
// Hide hides the window.
func (w *WebviewWindow) Hide() *WebviewWindow {
w.options.Hidden = true
if w.impl != nil {
w.impl.hide()
invokeSync(w.impl.hide)
}
return w
}
@ -199,38 +237,49 @@ func (w *WebviewWindow) Hide() *WebviewWindow {
func (w *WebviewWindow) SetURL(s string) *WebviewWindow {
w.options.URL = s
if w.impl != nil {
w.impl.setURL(s)
invokeSync(func() {
w.impl.setURL(s)
})
}
return w
}
// SetZoom sets the zoom level of the window.
func (w *WebviewWindow) SetZoom(magnification float64) *WebviewWindow {
w.options.Zoom = magnification
if w.impl != nil {
w.impl.setZoom(magnification)
invokeSync(func() {
w.impl.setZoom(magnification)
})
}
return w
}
// GetZoom returns the current zoom level of the window.
func (w *WebviewWindow) GetZoom() float64 {
if w.impl != nil {
return w.impl.getZoom()
return invokeSyncWithResult(w.impl.getZoom)
}
return 1
}
// SetResizable sets whether the window is resizable.
func (w *WebviewWindow) SetResizable(b bool) *WebviewWindow {
w.options.DisableResize = !b
if w.impl != nil {
w.impl.setResizable(b)
invokeSync(func() {
w.impl.setResizable(b)
})
}
return w
}
// Resizable returns true if the window is resizable.
func (w *WebviewWindow) Resizable() bool {
return !w.options.DisableResize
}
// SetMinSize sets the minimum size of the window.
func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) *WebviewWindow {
w.options.MinWidth = minWidth
w.options.MinHeight = minHeight
@ -251,13 +300,18 @@ func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) *WebviewWindow {
}
if w.impl != nil {
if newSize {
w.impl.setSize(newWidth, newHeight)
invokeSync(func() {
w.impl.setSize(newWidth, newHeight)
})
}
w.impl.setMinSize(minWidth, minHeight)
invokeSync(func() {
w.impl.setMinSize(minWidth, minHeight)
})
}
return w
}
// SetMaxSize sets the maximum size of the window.
func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) *WebviewWindow {
w.options.MaxWidth = maxWidth
w.options.MaxHeight = maxHeight
@ -278,13 +332,18 @@ func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) *WebviewWindow {
}
if w.impl != nil {
if newSize {
w.impl.setSize(newWidth, newHeight)
invokeSync(func() {
w.impl.setSize(newWidth, newHeight)
})
}
w.impl.setMaxSize(maxWidth, maxHeight)
invokeSync(func() {
w.impl.setMaxSize(maxWidth, maxHeight)
})
}
return w
}
// ExecJS executes the given javascript in the context of the window.
func (w *WebviewWindow) ExecJS(js string) {
if w.impl == nil {
return
@ -292,6 +351,7 @@ func (w *WebviewWindow) ExecJS(js string) {
w.impl.execJS(js)
}
// Fullscreen sets the window to fullscreen mode. Min/Max size constraints are disabled.
func (w *WebviewWindow) Fullscreen() *WebviewWindow {
if w.impl == nil {
w.options.StartState = WindowStateFullscreen
@ -299,7 +359,7 @@ func (w *WebviewWindow) Fullscreen() *WebviewWindow {
}
if !w.IsFullscreen() {
w.disableSizeConstraints()
w.impl.fullscreen()
invokeSync(w.impl.fullscreen)
}
return w
}
@ -307,7 +367,9 @@ func (w *WebviewWindow) Fullscreen() *WebviewWindow {
func (w *WebviewWindow) SetFullscreenButtonEnabled(enabled bool) *WebviewWindow {
w.options.FullscreenButtonEnabled = enabled
if w.impl != nil {
w.impl.setFullscreenButtonEnabled(enabled)
invokeSync(func() {
w.impl.setFullscreenButtonEnabled(enabled)
})
}
return w
}
@ -317,7 +379,15 @@ func (w *WebviewWindow) IsMinimised() bool {
if w.impl == nil {
return false
}
return w.impl.isMinimised()
return invokeSyncWithResult(w.impl.isMinimised)
}
// IsVisible returns true if the window is visible
func (w *WebviewWindow) IsVisible() bool {
if w.impl == nil {
return false
}
return invokeSyncWithResult(w.impl.isVisible)
}
// IsMaximised returns true if the window is maximised
@ -325,15 +395,19 @@ func (w *WebviewWindow) IsMaximised() bool {
if w.impl == nil {
return false
}
return w.impl.isMaximised()
return invokeSyncWithResult(w.impl.isMaximised)
}
// Size returns the size of the window
func (w *WebviewWindow) Size() (width int, height int) {
func (w *WebviewWindow) Size() (int, int) {
if w.impl == nil {
return 0, 0
}
return w.impl.size()
var width, height int
invokeSync(func() {
width, height = w.impl.size()
})
return width, height
}
// IsFullscreen returns true if the window is fullscreen
@ -343,13 +417,16 @@ func (w *WebviewWindow) IsFullscreen() bool {
if w.impl == nil {
return false
}
return w.impl.isFullscreen()
return invokeSyncWithResult(w.impl.isFullscreen)
}
func (w *WebviewWindow) SetBackgroundColour(colour *RGBA) *WebviewWindow {
// SetBackgroundColour sets the background colour of the window
func (w *WebviewWindow) SetBackgroundColour(colour RGBA) *WebviewWindow {
w.options.BackgroundColour = colour
if w.impl != nil {
w.impl.setBackgroundColour(colour)
invokeSync(func() {
w.impl.setBackgroundColour(colour)
})
}
return w
}
@ -358,170 +435,205 @@ func (w *WebviewWindow) handleMessage(message string) {
w.info(message)
// Check for special messages
if message == "test" {
w.SetTitle("Hello World")
invokeSync(func() {
w.SetTitle("Hello World")
})
}
w.info("ProcessMessage from front end:", message)
}
// Center centers the window on the screen
func (w *WebviewWindow) Center() {
if w.impl == nil {
return
}
w.impl.center()
invokeSync(w.impl.center)
}
func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(ctx *WindowEventContext)) {
// On registers a callback for the given window event
func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(ctx *WindowEventContext)) func() {
eventID := uint(eventType)
w.eventListenersLock.Lock()
defer w.eventListenersLock.Unlock()
w.eventListeners[eventID] = append(w.eventListeners[eventID], callback)
windowEventListener := &WindowEventListener{
callback: callback,
}
w.eventListeners[eventID] = append(w.eventListeners[eventID], windowEventListener)
if w.impl != nil {
w.impl.on(eventID)
}
return func() {
w.eventListenersLock.Lock()
defer w.eventListenersLock.Unlock()
w.eventListeners[eventID] = lo.Without(w.eventListeners[eventID], windowEventListener)
}
}
func (w *WebviewWindow) handleWindowEvent(id uint) {
w.eventListenersLock.RLock()
for _, callback := range w.eventListeners[id] {
go callback(blankWindowEventContext)
for _, listener := range w.eventListeners[id] {
go listener.callback(blankWindowEventContext)
}
w.eventListenersLock.RUnlock()
}
// Width returns the width of the window
func (w *WebviewWindow) Width() int {
if w.impl == nil {
return 0
}
return w.impl.width()
return invokeSyncWithResult(w.impl.width)
}
// Height returns the height of the window
func (w *WebviewWindow) Height() int {
if w.impl == nil {
return 0
}
return w.impl.height()
return invokeSyncWithResult(w.impl.height)
}
// Position returns the position of the window
func (w *WebviewWindow) Position() (int, int) {
w.implLock.RLock()
defer w.implLock.RUnlock()
if w.impl == nil {
return 0, 0
}
return w.impl.position()
var x, y int
invokeSync(func() {
x, y = w.impl.position()
})
return x, y
}
func (w *WebviewWindow) Destroy() {
if w.impl == nil {
return
}
w.impl.destroy()
// Cancel the callbacks
for _, cancelFunc := range w.cancellers {
cancelFunc()
}
invokeSync(w.impl.destroy)
}
// Reload reloads the page assets
func (w *WebviewWindow) Reload() {
if w.impl == nil {
return
}
w.impl.reload()
invokeSync(w.impl.reload)
}
// ForceReload forces the window to reload the page assets
func (w *WebviewWindow) ForceReload() {
if w.impl == nil {
return
}
w.impl.forceReload()
invokeSync(w.impl.forceReload)
}
// ToggleFullscreen toggles the window between fullscreen and normal
func (w *WebviewWindow) ToggleFullscreen() {
if w.impl == nil {
return
}
if w.IsFullscreen() {
w.UnFullscreen()
} else {
w.Fullscreen()
}
invokeSync(func() {
if w.IsFullscreen() {
w.UnFullscreen()
} else {
w.Fullscreen()
}
})
}
func (w *WebviewWindow) ToggleDevTools() {
if w.impl == nil {
return
}
w.impl.toggleDevTools()
invokeSync(w.impl.toggleDevTools)
}
// ZoomReset resets the zoom level of the webview content to 100%
func (w *WebviewWindow) ZoomReset() *WebviewWindow {
if w.impl != nil {
w.impl.zoomReset()
invokeSync(w.impl.zoomReset)
}
return w
}
// ZoomIn increases the zoom level of the webview content
func (w *WebviewWindow) ZoomIn() {
if w.impl == nil {
return
}
w.impl.zoomIn()
invokeSync(w.impl.zoomIn)
}
// ZoomOut decreases the zoom level of the webview content
func (w *WebviewWindow) ZoomOut() {
if w.impl == nil {
return
}
w.impl.zoomOut()
invokeSync(w.impl.zoomOut)
}
// Close closes the window
func (w *WebviewWindow) Close() {
if w.impl == nil {
return
}
w.impl.close()
}
func (w *WebviewWindow) Minimize() {
if w.impl == nil {
return
}
w.impl.minimise()
invokeSync(w.impl.close)
}
func (w *WebviewWindow) Zoom() {
if w.impl == nil {
return
}
w.impl.zoom()
invokeSync(w.impl.zoom)
}
// SetHTML sets the HTML of the window to the given html string.
func (w *WebviewWindow) SetHTML(html string) *WebviewWindow {
w.options.HTML = html
if w.impl != nil {
w.impl.setHTML(html)
invokeSync(func() {
w.impl.setHTML(html)
})
}
return w
}
// SetPosition sets the position of the window.
func (w *WebviewWindow) SetPosition(x, y int) *WebviewWindow {
w.options.X = x
w.options.Y = y
if w.impl != nil {
w.impl.setPosition(x, y)
invokeSync(func() {
w.impl.setPosition(x, y)
})
}
return w
}
// Minimise minimises the window.
func (w *WebviewWindow) Minimise() *WebviewWindow {
if w.impl == nil {
w.options.StartState = WindowStateMinimised
return w
}
if !w.IsMinimised() {
w.impl.minimise()
invokeSync(w.impl.minimise)
}
return w
}
// Maximise maximises the window. Min/Max size constraints are disabled.
func (w *WebviewWindow) Maximise() *WebviewWindow {
if w.impl == nil {
w.options.StartState = WindowStateMaximised
@ -529,74 +641,102 @@ func (w *WebviewWindow) Maximise() *WebviewWindow {
}
if !w.IsMaximised() {
w.disableSizeConstraints()
w.impl.maximise()
invokeSync(w.impl.maximise)
}
return w
}
// UnMinimise un-minimises the window. Min/Max size constraints are re-enabled.
func (w *WebviewWindow) UnMinimise() {
if w.impl == nil {
return
}
w.impl.unminimise()
if w.IsMinimised() {
invokeSync(w.impl.unminimise)
}
}
// UnMaximise un-maximises the window.
func (w *WebviewWindow) UnMaximise() {
if w.impl == nil {
return
}
w.enableSizeConstraints()
w.impl.unmaximise()
if w.IsMaximised() {
w.enableSizeConstraints()
invokeSync(w.impl.unmaximise)
}
}
// UnFullscreen un-fullscreens the window.
func (w *WebviewWindow) UnFullscreen() {
if w.impl == nil {
return
}
w.enableSizeConstraints()
w.impl.unfullscreen()
if w.IsFullscreen() {
w.enableSizeConstraints()
invokeSync(w.impl.unfullscreen)
}
}
// Restore restores the window to its previous state if it was previously minimised, maximised or fullscreen.
func (w *WebviewWindow) Restore() {
if w.impl == nil {
return
}
if w.IsMinimised() {
w.UnMinimise()
} else if w.IsMaximised() {
w.UnMaximise()
} else if w.IsFullscreen() {
w.UnFullscreen()
}
invokeSync(func() {
if w.IsMinimised() {
w.UnMinimise()
} else if w.IsMaximised() {
w.UnMaximise()
} else if w.IsFullscreen() {
w.UnFullscreen()
}
})
}
func (w *WebviewWindow) disableSizeConstraints() {
if w.impl == nil {
return
}
w.impl.setMinSize(0, 0)
w.impl.setMaxSize(0, 0)
invokeSync(func() {
if w.options.MinWidth > 0 && w.options.MinHeight > 0 {
w.impl.setMinSize(0, 0)
}
if w.options.MaxWidth > 0 && w.options.MaxHeight > 0 {
w.impl.setMaxSize(0, 0)
}
})
}
func (w *WebviewWindow) enableSizeConstraints() {
if w.impl == nil {
return
}
w.SetMinSize(w.options.MinWidth, w.options.MinHeight)
w.SetMaxSize(w.options.MaxWidth, w.options.MaxHeight)
invokeSync(func() {
if w.options.MinWidth > 0 && w.options.MinHeight > 0 {
w.SetMinSize(w.options.MinWidth, w.options.MinHeight)
}
if w.options.MaxWidth > 0 && w.options.MaxHeight > 0 {
w.SetMaxSize(w.options.MaxWidth, w.options.MaxHeight)
}
})
}
// GetScreen returns the screen that the window is on
func (w *WebviewWindow) GetScreen() (*Screen, error) {
if w.impl == nil {
return nil, nil
}
return w.impl.getScreen()
return invokeSyncWithResultAndError(w.impl.getScreen)
}
// SetFrameless removes the window frame and title bar
func (w *WebviewWindow) SetFrameless(frameless bool) *WebviewWindow {
w.options.Frameless = frameless
if w.impl != nil {
w.impl.setFrameless(frameless)
invokeSync(func() {
w.impl.setFrameless(frameless)
})
}
return w
}
@ -631,7 +771,7 @@ func (w *WebviewWindow) handleDragAndDropMessage(event *dragAndDropMessage) {
ctx := newWindowEventContext()
ctx.setDroppedFiles(event.filenames)
for _, listener := range w.eventListeners[uint(events.FilesDropped)] {
listener(ctx)
listener.callback(ctx)
}
}
@ -652,8 +792,17 @@ func (w *WebviewWindow) openContextMenu(data *ContextMenuData) {
w.impl.openContextMenu(menu, data)
}
// RegisterContextMenu registers a context menu and assigns it the given name.
func (w *WebviewWindow) RegisterContextMenu(name string, menu *Menu) {
w.contextMenusLock.Lock()
defer w.contextMenusLock.Unlock()
w.contextMenus[name] = menu
}
// NativeWindowHandle returns the platform native window handle for the window.
func (w *WebviewWindow) NativeWindowHandle() (uintptr, error) {
if w.impl == nil {
return 0, errors.New("native handle unavailable as window is not running")
}
return w.impl.nativeWindowHandle(), nil
}

View File

@ -703,6 +703,12 @@ static bool isFullScreen(void *window) {
return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen;
}
static bool isVisible(void *window) {
// get main window
WebviewWindow* nsWindow = (WebviewWindow*)window;
return (nsWindow.occlusionState & NSWindowOcclusionStateVisible) == NSWindowOcclusionStateVisible;
}
// windowSetFullScreen
static void windowSetFullScreen(void *window, bool fullscreen) {
if (isFullScreen(window)) {
@ -942,6 +948,14 @@ func (w *macosWebviewWindow) isFullscreen() bool {
})
}
func (w *macosWebviewWindow) isNormal() bool {
return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen()
}
func (w *macosWebviewWindow) isVisible() bool {
return bool(C.isVisible(w.nsWindow))
}
func (w *macosWebviewWindow) syncMainThreadReturningBool(fn func() bool) bool {
var wg sync.WaitGroup
wg.Add(1)
@ -1147,10 +1161,12 @@ func (w *macosWebviewWindow) run() {
})
}
func (w *macosWebviewWindow) setBackgroundColour(colour *RGBA) {
if colour == nil {
return
}
func (w *macosWebviewWindow) nativeWindowHandle() uintptr {
return uintptr(w.nsWindow)
}
func (w *macosWebviewWindow) setBackgroundColour(colour RGBA) {
C.windowSetBackgroundColour(w.nsWindow, C.int(colour.Red), C.int(colour.Green), C.int(colour.Blue), C.int(colour.Alpha))
}

View File

@ -3,7 +3,14 @@
package application
import (
"errors"
"fmt"
"strconv"
"unicode/utf16"
"unsafe"
"github.com/wailsapp/wails/v3/pkg/events"
"github.com/wailsapp/wails/v3/pkg/w32"
)
var showDevTools = func(window unsafe.Pointer) {}
@ -11,21 +18,35 @@ var showDevTools = func(window unsafe.Pointer) {}
type windowsWebviewWindow struct {
windowImpl unsafe.Pointer
parent *WebviewWindow
hwnd w32.HWND
// Fullscreen flags
isCurrentlyFullscreen bool
previousWindowStyle uint32
previousWindowExStyle uint32
previousWindowPlacement w32.WINDOWPLACEMENT
}
func (w *windowsWebviewWindow) nativeWindowHandle() uintptr {
return w.hwnd
}
func (w *windowsWebviewWindow) setTitle(title string) {
//TODO implement me
panic("implement me")
w32.SetWindowText(w.hwnd, title)
}
func (w *windowsWebviewWindow) setSize(width, height int) {
//TODO implement me
panic("implement me")
rect := w32.GetWindowRect(w.hwnd)
width, height = w.scaleWithWindowDPI(width, height)
w32.MoveWindow(w.hwnd, int(rect.Left), int(rect.Top), width, height, true)
}
func (w *windowsWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) {
//TODO implement me
panic("implement me")
position := w32.HWND_NOTOPMOST
if alwaysOnTop {
position = w32.HWND_TOPMOST
}
w32.SetWindowPos(w.hwnd, position, 0, 0, 0, 0, uint(w32.SWP_NOMOVE|w32.SWP_NOSIZE))
}
func (w *windowsWebviewWindow) setURL(url string) {
@ -34,18 +55,17 @@ func (w *windowsWebviewWindow) setURL(url string) {
}
func (w *windowsWebviewWindow) setResizable(resizable bool) {
//TODO implement me
panic("implement me")
w.setStyle(resizable, w32.WS_THICKFRAME)
}
func (w *windowsWebviewWindow) setMinSize(width, height int) {
//TODO implement me
panic("implement me")
w.parent.options.MinWidth = width
w.parent.options.MinHeight = height
}
func (w *windowsWebviewWindow) setMaxSize(width, height int) {
//TODO implement me
panic("implement me")
w.parent.options.MaxWidth = width
w.parent.options.MaxHeight = height
}
func (w *windowsWebviewWindow) execJS(js string) {
@ -53,44 +73,167 @@ func (w *windowsWebviewWindow) execJS(js string) {
panic("implement me")
}
func (w *windowsWebviewWindow) restore() {
//TODO implement me
panic("implement me")
func (w *windowsWebviewWindow) setBackgroundColour(color RGBA) {
w32.SetBackgroundColour(w.hwnd, color.Red, color.Green, color.Blue)
}
func (w *windowsWebviewWindow) setBackgroundColour(color *RGBA) {
//TODO implement me
panic("implement me")
func (w *windowsWebviewWindow) framelessWithDecorations() bool {
return w.parent.options.Frameless && !w.parent.options.Windows.DisableFramelessWindowDecorations
}
func (w *windowsWebviewWindow) run() {
//TODO implement me
panic("implement me")
options := w.parent.options
var exStyle uint
exStyle = w32.WS_EX_CONTROLPARENT | w32.WS_EX_APPWINDOW
if options.BackgroundType != BackgroundTypeSolid {
exStyle |= w32.WS_EX_NOREDIRECTIONBITMAP
}
if options.AlwaysOnTop {
exStyle |= w32.WS_EX_TOPMOST
}
w.hwnd = w32.CreateWindowEx(
exStyle,
windowClassName,
w32.MustStringToUTF16Ptr(options.Title),
w32.WS_OVERLAPPEDWINDOW,
w32.CW_USEDEFAULT,
w32.CW_USEDEFAULT,
options.Width,
options.Height,
0,
0,
w32.GetModuleHandle(""),
nil)
if w.hwnd == 0 {
panic("Unable to create window")
}
// Register the window with the application
windowsApp := globalApplication.impl.(*windowsApp)
windowsApp.registerWindow(w)
w.setResizable(!options.DisableResize)
if options.Frameless {
// Inform the application of the frame change this is needed to trigger the WM_NCCALCSIZE event.
// => https://learn.microsoft.com/en-us/windows/win32/dwm/customframe#removing-the-standard-frame
// This is normally done in WM_CREATE but we can't handle that there because that is emitted during CreateWindowEx
// and at that time we can't yet register the window for calling our WndProc method.
// This must be called after setResizable above!
rcClient := w32.GetWindowRect(w.hwnd)
w32.SetWindowPos(w.hwnd,
0,
int(rcClient.Left),
int(rcClient.Top),
int(rcClient.Right-rcClient.Left),
int(rcClient.Bottom-rcClient.Top),
w32.SWP_FRAMECHANGED)
}
// Icon
if !options.Windows.DisableIcon {
// App icon ID is 3
icon, err := NewIconFromResource(w32.GetModuleHandle(""), uint16(3))
if err == nil {
w.setIcon(icon)
}
} else {
w.disableIcon()
}
switch options.BackgroundType {
case BackgroundTypeSolid:
w.setBackgroundColour(options.BackgroundColour)
case BackgroundTypeTransparent:
case BackgroundTypeTranslucent:
w.setBackdropType(options.Windows.BackdropType)
}
// Process the theme
switch options.Windows.Theme {
case SystemDefault:
w.updateTheme(w32.IsCurrentlyDarkMode())
w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func() {
w.updateTheme(w32.IsCurrentlyDarkMode())
})
case Light:
w.updateTheme(false)
case Dark:
w.updateTheme(true)
}
// Process StartState
switch options.StartState {
case WindowStateMaximised:
if w.parent.Resizable() {
w.maximise()
}
case WindowStateMinimised:
w.minimise()
case WindowStateFullscreen:
w.fullscreen()
}
w.setForeground()
if !options.Hidden {
w.show()
w.update()
}
}
func (w *windowsWebviewWindow) center() {
//TODO implement me
panic("implement me")
w32.CenterWindow(w.hwnd)
}
func (w *windowsWebviewWindow) disableSizeConstraints() {
w.setMaxSize(0, 0)
w.setMinSize(0, 0)
}
func (w *windowsWebviewWindow) enableSizeConstraints() {
options := w.parent.options
if options.MinWidth > 0 || options.MinHeight > 0 {
w.setMinSize(options.MinWidth, options.MinHeight)
}
if options.MaxWidth > 0 || options.MaxHeight > 0 {
w.setMaxSize(options.MaxWidth, options.MaxHeight)
}
}
func (w *windowsWebviewWindow) size() (int, int) {
//TODO implement me
panic("implement me")
rect := w32.GetWindowRect(w.hwnd)
width := int(rect.Right - rect.Left)
height := int(rect.Bottom - rect.Top)
width, height = w.scaleToDefaultDPI(width, height)
return width, height
}
func (w *windowsWebviewWindow) setForeground() {
w32.SetForegroundWindow(w.hwnd)
}
func (w *windowsWebviewWindow) update() {
w32.UpdateWindow(w.hwnd)
}
func (w *windowsWebviewWindow) width() int {
//TODO implement me
panic("implement me")
width, _ := w.size()
return width
}
func (w *windowsWebviewWindow) height() int {
//TODO implement me
panic("implement me")
_, height := w.size()
return height
}
func (w *windowsWebviewWindow) position() (int, int) {
//TODO implement me
panic("implement me")
rect := w32.GetWindowRect(w.hwnd)
left, right := w.scaleToDefaultDPI(int(rect.Left), int(rect.Right))
return left, right
}
func (w *windowsWebviewWindow) destroy() {
@ -154,83 +297,181 @@ func (w *windowsWebviewWindow) setHTML(html string) {
}
func (w *windowsWebviewWindow) setPosition(x int, y int) {
//TODO implement me
panic("implement me")
x, y = w.scaleWithWindowDPI(x, y)
info := w32.GetMonitorInfoForWindow(w.hwnd)
workRect := info.RcWork
w32.SetWindowPos(w.hwnd, w32.HWND_TOP, int(workRect.Left)+x, int(workRect.Top)+y, 0, 0, w32.SWP_NOSIZE)
}
// on is used to indicate that a particular event should be listened for
func (w *windowsWebviewWindow) on(eventID uint) {
//TODO implement me
panic("implement me")
}
func (w *windowsWebviewWindow) minimise() {
//TODO implement me
panic("implement me")
w32.ShowWindow(w.hwnd, w32.SW_MINIMIZE)
}
func (w *windowsWebviewWindow) unminimise() {
//TODO implement me
panic("implement me")
w.restore()
}
func (w *windowsWebviewWindow) maximise() {
//TODO implement me
panic("implement me")
w32.ShowWindow(w.hwnd, w32.SW_MAXIMIZE)
}
func (w *windowsWebviewWindow) unmaximise() {
//TODO implement me
panic("implement me")
w.restore()
}
func (w *windowsWebviewWindow) restore() {
w32.ShowWindow(w.hwnd, w32.SW_RESTORE)
}
func (w *windowsWebviewWindow) fullscreen() {
//TODO implement me
panic("implement me")
if w.isFullscreen() {
return
}
if w.framelessWithDecorations() {
w32.ExtendFrameIntoClientArea(w.hwnd, false)
}
w.disableSizeConstraints()
w.previousWindowStyle = uint32(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE))
w.previousWindowExStyle = uint32(w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE))
monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTOPRIMARY)
var monitorInfo w32.MONITORINFO
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
if !w32.GetMonitorInfo(monitor, &monitorInfo) {
return
}
if !w32.GetWindowPlacement(w.hwnd, &w.previousWindowPlacement) {
return
}
// According to https://devblogs.microsoft.com/oldnewthing/20050505-04/?p=35703 one should use w32.WS_POPUP | w32.WS_VISIBLE
w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w.previousWindowStyle & ^uint32(w32.WS_OVERLAPPEDWINDOW) | (w32.WS_POPUP|w32.WS_VISIBLE))
w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle & ^uint32(w32.WS_EX_DLGMODALFRAME))
w.isCurrentlyFullscreen = true
w32.SetWindowPos(w.hwnd, w32.HWND_TOP,
int(monitorInfo.RcMonitor.Left),
int(monitorInfo.RcMonitor.Top),
int(monitorInfo.RcMonitor.Right-monitorInfo.RcMonitor.Left),
int(monitorInfo.RcMonitor.Bottom-monitorInfo.RcMonitor.Top),
w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED)
}
func (w *windowsWebviewWindow) unfullscreen() {
//TODO implement me
panic("implement me")
if !w.isFullscreen() {
return
}
if w.framelessWithDecorations() {
w32.ExtendFrameIntoClientArea(w.hwnd, true)
}
w32.SetWindowLong(w.hwnd, w32.GWL_STYLE, w.previousWindowStyle)
w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, w.previousWindowExStyle)
w32.SetWindowPlacement(w.hwnd, &w.previousWindowPlacement)
w.isCurrentlyFullscreen = false
w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0,
w32.SWP_NOMOVE|w32.SWP_NOSIZE|w32.SWP_NOZORDER|w32.SWP_NOOWNERZORDER|w32.SWP_FRAMECHANGED)
w.enableSizeConstraints()
}
func (w *windowsWebviewWindow) isMinimised() bool {
//TODO implement me
panic("implement me")
style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE))
return style&w32.WS_MINIMIZE != 0
}
func (w *windowsWebviewWindow) isMaximised() bool {
//TODO implement me
panic("implement me")
style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE))
return style&w32.WS_MAXIMIZE != 0
}
func (w *windowsWebviewWindow) isFullscreen() bool {
//TODO implement me
panic("implement me")
// TODO: Actually calculate this based on size of window against screen size
// => stffabi: This flag is essential since it indicates that we are in fullscreen mode even before the native properties
// reflect this, e.g. when needing to know if we are in fullscreen during a wndproc message.
// That's also why this flag is set before SetWindowPos in v2 in fullscreen/unfullscreen.
return w.isCurrentlyFullscreen
}
func (w *windowsWebviewWindow) disableSizeConstraints() {
//TODO implement me
panic("implement me")
func (w *windowsWebviewWindow) isNormal() bool {
return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen()
}
func (w *windowsWebviewWindow) setFullscreenButtonEnabled(enabled bool) {
//TODO implement me
panic("implement me")
func (w *windowsWebviewWindow) isVisible() bool {
style := uint32(w32.GetWindowLong(w.hwnd, w32.GWL_STYLE))
return style&w32.WS_VISIBLE != 0
}
func (w *windowsWebviewWindow) setFullscreenButtonEnabled(_ bool) {
// Unused in Windows
}
func (w *windowsWebviewWindow) show() {
//TODO implement me
panic("implement me")
w32.ShowWindow(w.hwnd, w32.SW_SHOW)
}
func (w *windowsWebviewWindow) hide() {
//TODO implement me
panic("implement me")
w32.ShowWindow(w.hwnd, w32.SW_HIDE)
}
// Get the screen for the current window
func (w *windowsWebviewWindow) getScreen() (*Screen, error) {
//TODO implement me
panic("implement me")
hMonitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTONEAREST)
var mi w32.MONITORINFOEX
mi.CbSize = uint32(unsafe.Sizeof(mi))
w32.GetMonitorInfoEx(hMonitor, &mi)
var thisScreen Screen
thisScreen.X = int(mi.RcMonitor.Left)
thisScreen.Y = int(mi.RcMonitor.Top)
thisScreen.Size = Size{
Width: int(mi.RcMonitor.Right - mi.RcMonitor.Left),
Height: int(mi.RcMonitor.Bottom - mi.RcMonitor.Top),
}
thisScreen.Bounds = Rect{
X: int(mi.RcMonitor.Left),
Y: int(mi.RcMonitor.Top),
Width: int(mi.RcMonitor.Right - mi.RcMonitor.Left),
Height: int(mi.RcMonitor.Bottom - mi.RcMonitor.Top),
}
thisScreen.WorkArea = Rect{
X: int(mi.RcWork.Left),
Y: int(mi.RcWork.Top),
Width: int(mi.RcWork.Right - mi.RcWork.Left),
Height: int(mi.RcWork.Bottom - mi.RcWork.Top),
}
thisScreen.ID = strconv.Itoa(int(hMonitor))
thisScreen.Name = string(utf16.Decode(mi.SzDevice[:]))
var xdpi, ydpi w32.UINT
w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &xdpi, &ydpi)
thisScreen.Scale = float32(xdpi) / 96.0
thisScreen.IsPrimary = mi.DwFlags&w32.MONITORINFOF_PRIMARY != 0
// TODO: Get screen rotation
// https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-devmodea
//// get display settings for monitor
//var dm w32.DEVMODE
//dm.DmSize = uint16(unsafe.Sizeof(dm))
//dm.DmDriverExtra = 0
//w32.EnumDisplaySettingsEx(&mi.SzDevice[0], w32.ENUM_CURRENT_SETTINGS, &dm, 0)
//
//// check display settings for rotation
//rotationAngle := dm.DmDi
//if rotationAngle == DMDO_0 {
// printf("Monitor is not rotated\n")
//} else if rotationAngle == DMDO_90 {
// printf("Monitor is rotated 90 degrees\n")
//} else if rotationAngle == DMDO_180 {
// printf("Monitor is rotated 180 degrees\n")
//} else if rotationAngle == DMDO_270 {
// printf("Monitor is rotated 270 degrees\n")
//} else {
// printf("Monitor is rotated at an unknown angle\n")
//}
return &thisScreen, nil
}
func (w *windowsWebviewWindow) setFrameless(b bool) {
@ -242,6 +483,7 @@ func newWindowImpl(parent *WebviewWindow) *windowsWebviewWindow {
result := &windowsWebviewWindow{
parent: parent,
}
return result
}
@ -251,3 +493,339 @@ func (w *windowsWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData
thisMenu.update()
//C.windowShowMenu(w.nsWindow, thisMenu.nsMenu, C.int(data.X), C.int(data.Y))
}
func (w *windowsWebviewWindow) setStyle(b bool, style int) {
currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_STYLE))
if currentStyle != 0 {
if b {
currentStyle |= style
} else {
currentStyle &^= style
}
w32.SetWindowLongPtr(w.hwnd, w32.GWL_STYLE, uintptr(currentStyle))
}
}
func (w *windowsWebviewWindow) setExStyle(b bool, style int) {
currentStyle := int(w32.GetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE))
if currentStyle != 0 {
if b {
currentStyle |= style
} else {
currentStyle &^= style
}
w32.SetWindowLongPtr(w.hwnd, w32.GWL_EXSTYLE, uintptr(currentStyle))
}
}
func (w *windowsWebviewWindow) setBackdropType(backdropType BackdropType) {
if !w32.IsWindowsVersionAtLeast(10, 0, 22621) {
var accent = w32.ACCENT_POLICY{
AccentState: w32.ACCENT_ENABLE_BLURBEHIND,
}
var data w32.WINDOWCOMPOSITIONATTRIBDATA
data.Attrib = w32.WCA_ACCENT_POLICY
data.PvData = w32.PVOID(&accent)
data.CbData = w32.SIZE_T(unsafe.Sizeof(accent))
w32.SetWindowCompositionAttribute(w.hwnd, &data)
} else {
backdropValue := backdropType
// We default to None, but in w32 None = 1 and Auto = 0
// So we check if the value given was Auto and set it to 0
if backdropType == Auto {
backdropValue = None
}
w32.DwmSetWindowAttribute(w.hwnd, w32.DwmwaSystemBackdropType, w32.LPCVOID(&backdropValue), uint32(unsafe.Sizeof(backdropValue)))
}
}
func (w *windowsWebviewWindow) setIcon(icon w32.HICON) {
w32.SendMessage(w.hwnd, w32.BM_SETIMAGE, w32.IMAGE_ICON, uintptr(icon))
}
func (w *windowsWebviewWindow) disableIcon() {
// TODO: If frameless, return
exStyle := w32.GetWindowLong(w.hwnd, w32.GWL_EXSTYLE)
w32.SetWindowLong(w.hwnd, w32.GWL_EXSTYLE, uint32(exStyle|w32.WS_EX_DLGMODALFRAME))
w32.SetWindowPos(w.hwnd, 0, 0, 0, 0, 0,
uint(
w32.SWP_FRAMECHANGED|
w32.SWP_NOMOVE|
w32.SWP_NOSIZE|
w32.SWP_NOZORDER),
)
}
func (w *windowsWebviewWindow) updateTheme(isDarkMode bool) {
if w32.IsCurrentlyHighContrastMode() {
return
}
if !w32.SupportsThemes() {
return
}
w32.SetTheme(w.hwnd, isDarkMode)
// Custom theme processing
customTheme := w.parent.options.Windows.CustomTheme
// Custom theme
if w32.SupportsCustomThemes() && customTheme != nil {
if w.isActive() {
if isDarkMode {
w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBar)
w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleText)
w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorder)
} else {
w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBar)
w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleText)
w32.SetBorderColour(w.hwnd, customTheme.LightModeBorder)
}
} else {
if isDarkMode {
w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBarInactive)
w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleTextInactive)
w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorderInactive)
} else {
w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBarInactive)
w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleTextInactive)
w32.SetBorderColour(w.hwnd, customTheme.LightModeBorderInactive)
}
}
}
}
func (w *windowsWebviewWindow) isActive() bool {
return w32.GetForegroundWindow() == w.hwnd
}
func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
switch msg {
case w32.WM_SIZE:
return 0
case w32.WM_CLOSE:
w32.PostMessage(w.hwnd, w32.WM_QUIT, 0, 0)
// Unregister the window with the application
windowsApp := globalApplication.impl.(*windowsApp)
windowsApp.unregisterWindow(w)
return 0
case w32.WM_GETMINMAXINFO:
mmi := (*w32.MINMAXINFO)(unsafe.Pointer(lparam))
hasConstraints := false
options := w.parent.options
if options.MinWidth > 0 || options.MinHeight > 0 {
hasConstraints = true
width, height := w.scaleWithWindowDPI(options.MinWidth, options.MinHeight)
if width > 0 {
mmi.PtMinTrackSize.X = int32(width)
}
if height > 0 {
mmi.PtMinTrackSize.Y = int32(height)
}
}
if options.MaxWidth > 0 || options.MaxHeight > 0 {
hasConstraints = true
width, height := w.scaleWithWindowDPI(options.MaxWidth, options.MaxHeight)
if width > 0 {
mmi.PtMaxTrackSize.X = int32(width)
}
if height > 0 {
mmi.PtMaxTrackSize.Y = int32(height)
}
}
if hasConstraints {
return 0
}
case w32.WM_DPICHANGED:
newWindowSize := (*w32.RECT)(unsafe.Pointer(lparam))
w32.SetWindowPos(w.hwnd,
uintptr(0),
int(newWindowSize.Left),
int(newWindowSize.Top),
int(newWindowSize.Right-newWindowSize.Left),
int(newWindowSize.Bottom-newWindowSize.Top),
w32.SWP_NOZORDER|w32.SWP_NOACTIVATE)
}
if options := w.parent.options; options.Frameless {
switch msg {
case w32.WM_ACTIVATE:
// If we want to have a frameless window but with the default frame decorations, extend the DWM client area.
// This Option is not affected by returning 0 in WM_NCCALCSIZE.
// As a result we have hidden the titlebar but still have the default window frame styling.
// See: https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea#remarks
if w.framelessWithDecorations() {
w32.ExtendFrameIntoClientArea(w.hwnd, true)
}
case w32.WM_NCHITTEST:
// Get the cursor position
x := int32(w32.LOWORD(uint32(lparam)))
y := int32(w32.HIWORD(uint32(lparam)))
ptCursor := w32.POINT{X: x, Y: y}
// Get the window rectangle
rcWindow := w32.GetWindowRect(w.hwnd)
// Determine if the cursor is in a resize area
bOnResizeBorder := false
resizeBorderWidth := int32(5) // change this to adjust the resize border width
if ptCursor.X >= rcWindow.Right-resizeBorderWidth {
bOnResizeBorder = true // right edge
}
if ptCursor.Y >= rcWindow.Bottom-resizeBorderWidth {
bOnResizeBorder = true // bottom edge
}
if ptCursor.X <= rcWindow.Left+resizeBorderWidth {
bOnResizeBorder = true // left edge
}
if ptCursor.Y <= rcWindow.Top+resizeBorderWidth {
bOnResizeBorder = true // top edge
}
// Return the appropriate value
if bOnResizeBorder {
if ptCursor.X >= rcWindow.Right-resizeBorderWidth && ptCursor.Y >= rcWindow.Bottom-resizeBorderWidth {
return w32.HTBOTTOMRIGHT
} else if ptCursor.X <= rcWindow.Left+resizeBorderWidth && ptCursor.Y >= rcWindow.Bottom-resizeBorderWidth {
return w32.HTBOTTOMLEFT
} else if ptCursor.X >= rcWindow.Right-resizeBorderWidth && ptCursor.Y <= rcWindow.Top+resizeBorderWidth {
return w32.HTTOPRIGHT
} else if ptCursor.X <= rcWindow.Left+resizeBorderWidth && ptCursor.Y <= rcWindow.Top+resizeBorderWidth {
return w32.HTTOPLEFT
} else if ptCursor.X >= rcWindow.Right-resizeBorderWidth {
return w32.HTRIGHT
} else if ptCursor.Y >= rcWindow.Bottom-resizeBorderWidth {
return w32.HTBOTTOM
} else if ptCursor.X <= rcWindow.Left+resizeBorderWidth {
return w32.HTLEFT
} else if ptCursor.Y <= rcWindow.Top+resizeBorderWidth {
return w32.HTTOP
}
}
return w32.HTCLIENT
case w32.WM_NCCALCSIZE:
// Disable the standard frame by allowing the client area to take the full
// window size.
// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks
// This hides the titlebar and also disables the resizing from user interaction because the standard frame is not
// shown. We still need the WS_THICKFRAME style to enable resizing from the frontend.
if wparam != 0 {
rgrc := (*w32.RECT)(unsafe.Pointer(lparam))
if w.isCurrentlyFullscreen {
// In Full-Screen mode we don't need to adjust anything
// It essential we have the flag here, that is set before SetWindowPos in fullscreen/unfullscreen
// because the native size might not yet reflect we are in fullscreen during this event!
// TODO: w.chromium.SetPadding(edge.Rect{})
} else if w.isMaximised() {
// If the window is maximized we must adjust the client area to the work area of the monitor. Otherwise
// some content goes beyond the visible part of the monitor.
// Make sure to use the provided RECT to get the monitor, because during maximizig there might be
// a wrong monitor returned in multi screen mode when using MonitorFromWindow.
// See: https://github.com/MicrosoftEdge/WebView2Feedback/issues/2549
monitor := w32.MonitorFromRect(rgrc, w32.MONITOR_DEFAULTTONULL)
var monitorInfo w32.MONITORINFO
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
if monitor != 0 && w32.GetMonitorInfo(monitor, &monitorInfo) {
*rgrc = monitorInfo.RcWork
maxWidth := options.MaxWidth
maxHeight := options.MaxHeight
if maxWidth > 0 || maxHeight > 0 {
var dpiX, dpiY uint
w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
maxWidth := int32(ScaleWithDPI(maxWidth, dpiX))
if maxWidth > 0 && rgrc.Right-rgrc.Left > maxWidth {
rgrc.Right = rgrc.Left + maxWidth
}
maxHeight := int32(ScaleWithDPI(maxHeight, dpiY))
if maxHeight > 0 && rgrc.Bottom-rgrc.Top > maxHeight {
rgrc.Bottom = rgrc.Top + maxHeight
}
}
}
// TODO: w.chromium.SetPadding(edge.Rect{})
} else {
// This is needed to workaround the resize flickering in frameless mode with WindowDecorations
// See: https://stackoverflow.com/a/6558508
// The workaround originally suggests to decrese the bottom 1px, but that seems to bring up a thin
// white line on some Windows-Versions, due to DrawBackground using also this reduces ClientSize.
// Increasing the bottom also worksaround the flickering but we would loose 1px of the WebView content
// therefore let's pad the content with 1px at the bottom.
rgrc.Bottom += 1
// TODO: w.chromium.SetPadding(edge.Rect{Bottom: 1})
}
return 0
}
}
}
return w32.DefWindowProc(w.hwnd, msg, wparam, lparam)
}
func (w *windowsWebviewWindow) DPI() (w32.UINT, w32.UINT) {
if w32.HasGetDpiForWindowFunc() {
// GetDpiForWindow is supported beginning with Windows 10, 1607 and is the most accureate
// one, especially it is consistent with the WM_DPICHANGED event.
dpi := w32.GetDpiForWindow(w.hwnd)
return dpi, dpi
}
if w32.HasGetDPIForMonitorFunc() {
// GetDpiForWindow is supported beginning with Windows 8.1
monitor := w32.MonitorFromWindow(w.hwnd, w32.MONITOR_DEFAULTTONEAREST)
if monitor == 0 {
return 0, 0
}
var dpiX, dpiY w32.UINT
w32.GetDPIForMonitor(monitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
return dpiX, dpiY
}
// If none of the above is supported fallback to the System DPI.
screen := w32.GetDC(0)
x := w32.GetDeviceCaps(screen, w32.LOGPIXELSX)
y := w32.GetDeviceCaps(screen, w32.LOGPIXELSY)
w32.ReleaseDC(0, screen)
return w32.UINT(x), w32.UINT(y)
}
func (w *windowsWebviewWindow) scaleWithWindowDPI(width, height int) (int, int) {
dpix, dpiy := w.DPI()
scaledWidth := ScaleWithDPI(width, dpix)
scaledHeight := ScaleWithDPI(height, dpiy)
return scaledWidth, scaledHeight
}
func (w *windowsWebviewWindow) scaleToDefaultDPI(width, height int) (int, int) {
dpix, dpiy := w.DPI()
scaledWidth := ScaleToDefaultDPI(width, dpix)
scaledHeight := ScaleToDefaultDPI(height, dpiy)
return scaledWidth, scaledHeight
}
func ScaleWithDPI(pixels int, dpi uint) int {
return (pixels * int(dpi)) / 96
}
func ScaleToDefaultDPI(pixels int, dpi uint) int {
return (pixels * 96) / int(dpi)
}
func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error) {
var err error
var result w32.HICON
if result = w32.LoadIconWithResourceID(instance, resId); result == 0 {
err = errors.New(fmt.Sprintf("Cannot load icon from resource with id %v", resId))
}
return result, err
}

View File

@ -260,3 +260,25 @@ func newMacEvents() macEvents {
WindowFileDraggingExited: 1145,
}
}
var Windows = newWindowsEvents()
type windowsEvents struct {
SystemThemeChanged ApplicationEventType
APMPowerStatusChange ApplicationEventType
APMSuspend ApplicationEventType
APMResumeAutomatic ApplicationEventType
APMResumeSuspend ApplicationEventType
APMPowerSettingChange ApplicationEventType
}
func newWindowsEvents() windowsEvents {
return windowsEvents{
SystemThemeChanged: 1146,
APMPowerStatusChange: 1147,
APMSuspend: 1148,
APMResumeAutomatic: 1149,
APMResumeSuspend: 1150,
APMPowerSettingChange: 1151,
}
}

View File

@ -120,4 +120,9 @@ mac:WebViewDidCommitNavigation
mac:WindowFileDraggingEntered
mac:WindowFileDraggingPerformed
mac:WindowFileDraggingExited
windows:SystemThemeChanged
windows:APMPowerStatusChange
windows:APMSuspend
windows:APMResumeAutomatic
windows:APMResumeSuspend
windows:APMPowerSettingChange

143
v3/pkg/w32/clipboard.go Normal file
View File

@ -0,0 +1,143 @@
//go:build windows
/*
* Based on code originally from https://github.com/atotto/clipboard. Copyright (c) 2013 Ato Araki. All rights reserved.
*/
package w32
import (
"runtime"
"syscall"
"time"
"unsafe"
)
const (
cfUnicodetext = 13
gmemMoveable = 0x0002
)
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
func waitOpenClipboard() error {
started := time.Now()
limit := started.Add(time.Second)
var r uintptr
var err error
for time.Now().Before(limit) {
r, _, err = procOpenClipboard.Call(0)
if r != 0 {
return nil
}
time.Sleep(time.Millisecond)
}
return err
}
func GetClipboardText() (string, error) {
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if formatAvailable, _, err := procIsClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
return "", err
}
err := waitOpenClipboard()
if err != nil {
return "", err
}
h, _, err := procGetClipboardData.Call(cfUnicodetext)
if h == 0 {
_, _, _ = procCloseClipboard.Call()
return "", err
}
l, _, err := kernelGlobalLock.Call(h)
if l == 0 {
_, _, _ = procCloseClipboard.Call()
return "", err
}
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
r, _, err := kernelGlobalUnlock.Call(h)
if r == 0 {
_, _, _ = procCloseClipboard.Call()
return "", err
}
closed, _, err := procCloseClipboard.Call()
if closed == 0 {
return "", err
}
return text, nil
}
func SetClipboardText(text string) error {
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := waitOpenClipboard()
if err != nil {
return err
}
r, _, err := procEmptyClipboard.Call(0)
if r == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
data, err := syscall.UTF16FromString(text)
if err != nil {
return err
}
// "If the hMem parameter identifies a memory object, the object must have
// been allocated using the function with the GMEM_MOVEABLE flag."
h, _, err := kernelGlobalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
if h == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
defer func() {
if h != 0 {
kernelGlobalFree.Call(h)
}
}()
l, _, err := kernelGlobalLock.Call(h)
if l == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
r, _, err = kernelLstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
if r == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
r, _, err = kernelGlobalUnlock.Call(h)
if r == 0 {
if err.(syscall.Errno) != 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
}
r, _, err = procSetClipboardData.Call(cfUnicodetext, h)
if r == 0 {
_, _, _ = procCloseClipboard.Call()
return err
}
h = 0 // suppress deferred cleanup
closed, _, err := procCloseClipboard.Call()
if closed == 0 {
return err
}
return nil
}

112
v3/pkg/w32/comctl32.go Normal file
View File

@ -0,0 +1,112 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modcomctl32 = syscall.NewLazyDLL("comctl32.dll")
procInitCommonControlsEx = modcomctl32.NewProc("InitCommonControlsEx")
procImageList_Create = modcomctl32.NewProc("ImageList_Create")
procImageList_Destroy = modcomctl32.NewProc("ImageList_Destroy")
procImageList_GetImageCount = modcomctl32.NewProc("ImageList_GetImageCount")
procImageList_SetImageCount = modcomctl32.NewProc("ImageList_SetImageCount")
procImageList_Add = modcomctl32.NewProc("ImageList_Add")
procImageList_ReplaceIcon = modcomctl32.NewProc("ImageList_ReplaceIcon")
procImageList_Remove = modcomctl32.NewProc("ImageList_Remove")
procTrackMouseEvent = modcomctl32.NewProc("_TrackMouseEvent")
)
func InitCommonControlsEx(lpInitCtrls *INITCOMMONCONTROLSEX) bool {
ret, _, _ := procInitCommonControlsEx.Call(
uintptr(unsafe.Pointer(lpInitCtrls)))
return ret != 0
}
func ImageList_Create(cx, cy int, flags uint, cInitial, cGrow int) HIMAGELIST {
ret, _, _ := procImageList_Create.Call(
uintptr(cx),
uintptr(cy),
uintptr(flags),
uintptr(cInitial),
uintptr(cGrow))
if ret == 0 {
panic("Create image list failed")
}
return HIMAGELIST(ret)
}
func ImageList_Destroy(himl HIMAGELIST) bool {
ret, _, _ := procImageList_Destroy.Call(
uintptr(himl))
return ret != 0
}
func ImageList_GetImageCount(himl HIMAGELIST) int {
ret, _, _ := procImageList_GetImageCount.Call(
uintptr(himl))
return int(ret)
}
func ImageList_SetImageCount(himl HIMAGELIST, uNewCount uint) bool {
ret, _, _ := procImageList_SetImageCount.Call(
uintptr(himl),
uintptr(uNewCount))
return ret != 0
}
func ImageList_Add(himl HIMAGELIST, hbmImage, hbmMask HBITMAP) int {
ret, _, _ := procImageList_Add.Call(
uintptr(himl),
uintptr(hbmImage),
uintptr(hbmMask))
return int(ret)
}
func ImageList_ReplaceIcon(himl HIMAGELIST, i int, hicon HICON) int {
ret, _, _ := procImageList_ReplaceIcon.Call(
uintptr(himl),
uintptr(i),
uintptr(hicon))
return int(ret)
}
func ImageList_AddIcon(himl HIMAGELIST, hicon HICON) int {
return ImageList_ReplaceIcon(himl, -1, hicon)
}
func ImageList_Remove(himl HIMAGELIST, i int) bool {
ret, _, _ := procImageList_Remove.Call(
uintptr(himl),
uintptr(i))
return ret != 0
}
func ImageList_RemoveAll(himl HIMAGELIST) bool {
return ImageList_Remove(himl, -1)
}
func TrackMouseEvent(tme *TRACKMOUSEEVENT) bool {
ret, _, _ := procTrackMouseEvent.Call(
uintptr(unsafe.Pointer(tme)))
return ret != 0
}

40
v3/pkg/w32/comdlg32.go Normal file
View File

@ -0,0 +1,40 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modcomdlg32 = syscall.NewLazyDLL("comdlg32.dll")
procGetSaveFileName = modcomdlg32.NewProc("GetSaveFileNameW")
procGetOpenFileName = modcomdlg32.NewProc("GetOpenFileNameW")
procCommDlgExtendedError = modcomdlg32.NewProc("CommDlgExtendedError")
)
func GetOpenFileName(ofn *OPENFILENAME) bool {
ret, _, _ := procGetOpenFileName.Call(
uintptr(unsafe.Pointer(ofn)))
return ret != 0
}
func GetSaveFileName(ofn *OPENFILENAME) bool {
ret, _, _ := procGetSaveFileName.Call(
uintptr(unsafe.Pointer(ofn)))
return ret != 0
}
func CommDlgExtendedError() uint {
ret, _, _ := procCommDlgExtendedError.Call()
return uint(ret)
}

3551
v3/pkg/w32/constants.go Normal file

File diff suppressed because it is too large Load Diff

83
v3/pkg/w32/consts.go Normal file
View File

@ -0,0 +1,83 @@
//go:build windows
package w32
import (
"golang.org/x/sys/windows/registry"
"strconv"
"syscall"
)
var (
modwingdi = syscall.NewLazyDLL("gdi32.dll")
procCreateSolidBrush = modwingdi.NewProc("CreateSolidBrush")
)
var (
kernel32 = syscall.NewLazyDLL("kernel32")
kernelGlobalAlloc = kernel32.NewProc("GlobalAlloc")
kernelGlobalFree = kernel32.NewProc("GlobalFree")
kernelGlobalLock = kernel32.NewProc("GlobalLock")
kernelGlobalUnlock = kernel32.NewProc("GlobalUnlock")
kernelLstrcpy = kernel32.NewProc("lstrcpyW")
)
var windowsVersion, _ = getWindowsVersionInfo()
func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
return windowsVersion.Major >= major &&
windowsVersion.Minor >= minor &&
windowsVersion.Build >= buildNumber
}
type WindowsVersionInfo struct {
Major int
Minor int
Build int
DisplayVersion string
}
func (w *WindowsVersionInfo) IsWindowsVersionAtLeast(major, minor, buildNumber int) bool {
return w.Major >= major && w.Minor >= minor && w.Build >= buildNumber
}
func getWindowsVersionInfo() (*WindowsVersionInfo, error) {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil {
return nil, err
}
return &WindowsVersionInfo{
Major: regDWORDKeyAsInt(key, "CurrentMajorVersionNumber"),
Minor: regDWORDKeyAsInt(key, "CurrentMinorVersionNumber"),
Build: regStringKeyAsInt(key, "CurrentBuildNumber"),
DisplayVersion: regKeyAsString(key, "DisplayVersion"),
}, nil
}
func regDWORDKeyAsInt(key registry.Key, name string) int {
result, _, err := key.GetIntegerValue(name)
if err != nil {
return -1
}
return int(result)
}
func regStringKeyAsInt(key registry.Key, name string) int {
resultStr, _, err := key.GetStringValue(name)
if err != nil {
return -1
}
result, err := strconv.Atoi(resultStr)
if err != nil {
return -1
}
return result
}
func regKeyAsString(key registry.Key, name string) string {
resultStr, _, err := key.GetStringValue(name)
if err != nil {
return ""
}
return resultStr
}

36
v3/pkg/w32/dwmapi.go Normal file
View File

@ -0,0 +1,36 @@
//go:build windows
package w32
import (
"syscall"
"unsafe"
)
var (
moddwmapi = syscall.NewLazyDLL("dwmapi.dll")
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
procDwmExtendFrameIntoClientArea = moddwmapi.NewProc("DwmExtendFrameIntoClientArea")
)
func DwmSetWindowAttribute(hwnd HWND, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute LPCVOID, cbAttribute uint32) HRESULT {
ret, _, _ := procDwmSetWindowAttribute.Call(
hwnd,
uintptr(dwAttribute),
uintptr(pvAttribute),
uintptr(cbAttribute))
return HRESULT(ret)
}
func dwmExtendFrameIntoClientArea(hwnd uintptr, margins *MARGINS) error {
ret, _, _ := procDwmExtendFrameIntoClientArea.Call(
hwnd,
uintptr(unsafe.Pointer(margins)))
if ret != 0 {
return syscall.GetLastError()
}
return nil
}

526
v3/pkg/w32/gdi32.go Normal file
View File

@ -0,0 +1,526 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modgdi32 = syscall.NewLazyDLL("gdi32.dll")
procGetDeviceCaps = modgdi32.NewProc("GetDeviceCaps")
procDeleteObject = modgdi32.NewProc("DeleteObject")
procCreateFontIndirect = modgdi32.NewProc("CreateFontIndirectW")
procAbortDoc = modgdi32.NewProc("AbortDoc")
procBitBlt = modgdi32.NewProc("BitBlt")
procPatBlt = modgdi32.NewProc("PatBlt")
procCloseEnhMetaFile = modgdi32.NewProc("CloseEnhMetaFile")
procCopyEnhMetaFile = modgdi32.NewProc("CopyEnhMetaFileW")
procCreateBrushIndirect = modgdi32.NewProc("CreateBrushIndirect")
procCreateCompatibleDC = modgdi32.NewProc("CreateCompatibleDC")
procCreateDC = modgdi32.NewProc("CreateDCW")
procCreateDIBSection = modgdi32.NewProc("CreateDIBSection")
procCreateEnhMetaFile = modgdi32.NewProc("CreateEnhMetaFileW")
procCreateIC = modgdi32.NewProc("CreateICW")
procDeleteDC = modgdi32.NewProc("DeleteDC")
procDeleteEnhMetaFile = modgdi32.NewProc("DeleteEnhMetaFile")
procEllipse = modgdi32.NewProc("Ellipse")
procEndDoc = modgdi32.NewProc("EndDoc")
procEndPage = modgdi32.NewProc("EndPage")
procExtCreatePen = modgdi32.NewProc("ExtCreatePen")
procGetEnhMetaFile = modgdi32.NewProc("GetEnhMetaFileW")
procGetEnhMetaFileHeader = modgdi32.NewProc("GetEnhMetaFileHeader")
procGetObject = modgdi32.NewProc("GetObjectW")
procGetStockObject = modgdi32.NewProc("GetStockObject")
procGetTextExtentExPoint = modgdi32.NewProc("GetTextExtentExPointW")
procGetTextExtentPoint32 = modgdi32.NewProc("GetTextExtentPoint32W")
procGetTextMetrics = modgdi32.NewProc("GetTextMetricsW")
procLineTo = modgdi32.NewProc("LineTo")
procMoveToEx = modgdi32.NewProc("MoveToEx")
procPlayEnhMetaFile = modgdi32.NewProc("PlayEnhMetaFile")
procRectangle = modgdi32.NewProc("Rectangle")
procResetDC = modgdi32.NewProc("ResetDCW")
procSelectObject = modgdi32.NewProc("SelectObject")
procSetBkMode = modgdi32.NewProc("SetBkMode")
procSetBrushOrgEx = modgdi32.NewProc("SetBrushOrgEx")
procSetStretchBltMode = modgdi32.NewProc("SetStretchBltMode")
procSetTextColor = modgdi32.NewProc("SetTextColor")
procSetBkColor = modgdi32.NewProc("SetBkColor")
procStartDoc = modgdi32.NewProc("StartDocW")
procStartPage = modgdi32.NewProc("StartPage")
procStretchBlt = modgdi32.NewProc("StretchBlt")
procSetDIBitsToDevice = modgdi32.NewProc("SetDIBitsToDevice")
procChoosePixelFormat = modgdi32.NewProc("ChoosePixelFormat")
procDescribePixelFormat = modgdi32.NewProc("DescribePixelFormat")
procGetEnhMetaFilePixelFormat = modgdi32.NewProc("GetEnhMetaFilePixelFormat")
procGetPixelFormat = modgdi32.NewProc("GetPixelFormat")
procSetPixelFormat = modgdi32.NewProc("SetPixelFormat")
procSwapBuffers = modgdi32.NewProc("SwapBuffers")
)
func GetDeviceCaps(hdc HDC, index int) int {
ret, _, _ := procGetDeviceCaps.Call(
uintptr(hdc),
uintptr(index))
return int(ret)
}
func DeleteObject(hObject HGDIOBJ) bool {
ret, _, _ := procDeleteObject.Call(
uintptr(hObject))
return ret != 0
}
func CreateFontIndirect(logFont *LOGFONT) HFONT {
ret, _, _ := procCreateFontIndirect.Call(
uintptr(unsafe.Pointer(logFont)))
return HFONT(ret)
}
func AbortDoc(hdc HDC) int {
ret, _, _ := procAbortDoc.Call(
uintptr(hdc))
return int(ret)
}
func BitBlt(hdcDest HDC, nXDest, nYDest, nWidth, nHeight int, hdcSrc HDC, nXSrc, nYSrc int, dwRop uint) {
ret, _, _ := procBitBlt.Call(
uintptr(hdcDest),
uintptr(nXDest),
uintptr(nYDest),
uintptr(nWidth),
uintptr(nHeight),
uintptr(hdcSrc),
uintptr(nXSrc),
uintptr(nYSrc),
uintptr(dwRop))
if ret == 0 {
panic("BitBlt failed")
}
}
func PatBlt(hdc HDC, nXLeft, nYLeft, nWidth, nHeight int, dwRop uint) {
ret, _, _ := procPatBlt.Call(
uintptr(hdc),
uintptr(nXLeft),
uintptr(nYLeft),
uintptr(nWidth),
uintptr(nHeight),
uintptr(dwRop))
if ret == 0 {
panic("PatBlt failed")
}
}
func CloseEnhMetaFile(hdc HDC) HENHMETAFILE {
ret, _, _ := procCloseEnhMetaFile.Call(
uintptr(hdc))
return HENHMETAFILE(ret)
}
func CopyEnhMetaFile(hemfSrc HENHMETAFILE, lpszFile *uint16) HENHMETAFILE {
ret, _, _ := procCopyEnhMetaFile.Call(
uintptr(hemfSrc),
uintptr(unsafe.Pointer(lpszFile)))
return HENHMETAFILE(ret)
}
func CreateBrushIndirect(lplb *LOGBRUSH) HBRUSH {
ret, _, _ := procCreateBrushIndirect.Call(
uintptr(unsafe.Pointer(lplb)))
return HBRUSH(ret)
}
func CreateCompatibleDC(hdc HDC) HDC {
ret, _, _ := procCreateCompatibleDC.Call(
uintptr(hdc))
if ret == 0 {
panic("Create compatible DC failed")
}
return HDC(ret)
}
func CreateDC(lpszDriver, lpszDevice, lpszOutput *uint16, lpInitData *DEVMODE) HDC {
ret, _, _ := procCreateDC.Call(
uintptr(unsafe.Pointer(lpszDriver)),
uintptr(unsafe.Pointer(lpszDevice)),
uintptr(unsafe.Pointer(lpszOutput)),
uintptr(unsafe.Pointer(lpInitData)))
return HDC(ret)
}
func CreateDIBSection(hdc HDC, pbmi *BITMAPINFO, iUsage uint, ppvBits *unsafe.Pointer, hSection HANDLE, dwOffset uint) HBITMAP {
ret, _, _ := procCreateDIBSection.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(pbmi)),
uintptr(iUsage),
uintptr(unsafe.Pointer(ppvBits)),
uintptr(hSection),
uintptr(dwOffset))
return HBITMAP(ret)
}
func CreateEnhMetaFile(hdcRef HDC, lpFilename *uint16, lpRect *RECT, lpDescription *uint16) HDC {
ret, _, _ := procCreateEnhMetaFile.Call(
uintptr(hdcRef),
uintptr(unsafe.Pointer(lpFilename)),
uintptr(unsafe.Pointer(lpRect)),
uintptr(unsafe.Pointer(lpDescription)))
return HDC(ret)
}
func CreateIC(lpszDriver, lpszDevice, lpszOutput *uint16, lpdvmInit *DEVMODE) HDC {
ret, _, _ := procCreateIC.Call(
uintptr(unsafe.Pointer(lpszDriver)),
uintptr(unsafe.Pointer(lpszDevice)),
uintptr(unsafe.Pointer(lpszOutput)),
uintptr(unsafe.Pointer(lpdvmInit)))
return HDC(ret)
}
func DeleteDC(hdc HDC) bool {
ret, _, _ := procDeleteDC.Call(
uintptr(hdc))
return ret != 0
}
func DeleteEnhMetaFile(hemf HENHMETAFILE) bool {
ret, _, _ := procDeleteEnhMetaFile.Call(
uintptr(hemf))
return ret != 0
}
func Ellipse(hdc HDC, nLeftRect, nTopRect, nRightRect, nBottomRect int32) bool {
ret, _, _ := procEllipse.Call(
uintptr(hdc),
uintptr(nLeftRect),
uintptr(nTopRect),
uintptr(nRightRect),
uintptr(nBottomRect))
return ret != 0
}
func EndDoc(hdc HDC) int {
ret, _, _ := procEndDoc.Call(
uintptr(hdc))
return int(ret)
}
func EndPage(hdc HDC) int {
ret, _, _ := procEndPage.Call(
uintptr(hdc))
return int(ret)
}
func ExtCreatePen(dwPenStyle, dwWidth uint, lplb *LOGBRUSH, dwStyleCount uint, lpStyle *uint) HPEN {
ret, _, _ := procExtCreatePen.Call(
uintptr(dwPenStyle),
uintptr(dwWidth),
uintptr(unsafe.Pointer(lplb)),
uintptr(dwStyleCount),
uintptr(unsafe.Pointer(lpStyle)))
return HPEN(ret)
}
func GetEnhMetaFile(lpszMetaFile *uint16) HENHMETAFILE {
ret, _, _ := procGetEnhMetaFile.Call(
uintptr(unsafe.Pointer(lpszMetaFile)))
return HENHMETAFILE(ret)
}
func GetEnhMetaFileHeader(hemf HENHMETAFILE, cbBuffer uint, lpemh *ENHMETAHEADER) uint {
ret, _, _ := procGetEnhMetaFileHeader.Call(
uintptr(hemf),
uintptr(cbBuffer),
uintptr(unsafe.Pointer(lpemh)))
return uint(ret)
}
func GetObject(hgdiobj HGDIOBJ, cbBuffer uintptr, lpvObject unsafe.Pointer) int {
ret, _, _ := procGetObject.Call(
uintptr(hgdiobj),
uintptr(cbBuffer),
uintptr(lpvObject))
return int(ret)
}
func GetStockObject(fnObject int) HGDIOBJ {
ret, _, _ := procGetDeviceCaps.Call(
uintptr(fnObject))
return HGDIOBJ(ret)
}
func GetTextExtentExPoint(hdc HDC, lpszStr *uint16, cchString, nMaxExtent int, lpnFit, alpDx *int, lpSize *SIZE) bool {
ret, _, _ := procGetTextExtentExPoint.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lpszStr)),
uintptr(cchString),
uintptr(nMaxExtent),
uintptr(unsafe.Pointer(lpnFit)),
uintptr(unsafe.Pointer(alpDx)),
uintptr(unsafe.Pointer(lpSize)))
return ret != 0
}
func GetTextExtentPoint32(hdc HDC, lpString *uint16, c int, lpSize *SIZE) bool {
ret, _, _ := procGetTextExtentPoint32.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lpString)),
uintptr(c),
uintptr(unsafe.Pointer(lpSize)))
return ret != 0
}
func GetTextMetrics(hdc HDC, lptm *TEXTMETRIC) bool {
ret, _, _ := procGetTextMetrics.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lptm)))
return ret != 0
}
func LineTo(hdc HDC, nXEnd, nYEnd int32) bool {
ret, _, _ := procLineTo.Call(
uintptr(hdc),
uintptr(nXEnd),
uintptr(nYEnd))
return ret != 0
}
func MoveToEx(hdc HDC, x, y int, lpPoint *POINT) bool {
ret, _, _ := procMoveToEx.Call(
uintptr(hdc),
uintptr(x),
uintptr(y),
uintptr(unsafe.Pointer(lpPoint)))
return ret != 0
}
func PlayEnhMetaFile(hdc HDC, hemf HENHMETAFILE, lpRect *RECT) bool {
ret, _, _ := procPlayEnhMetaFile.Call(
uintptr(hdc),
uintptr(hemf),
uintptr(unsafe.Pointer(lpRect)))
return ret != 0
}
func Rectangle(hdc HDC, nLeftRect, nTopRect, nRightRect, nBottomRect int32) bool {
ret, _, _ := procRectangle.Call(
uintptr(hdc),
uintptr(nLeftRect),
uintptr(nTopRect),
uintptr(nRightRect),
uintptr(nBottomRect))
return ret != 0
}
func ResetDC(hdc HDC, lpInitData *DEVMODE) HDC {
ret, _, _ := procResetDC.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lpInitData)))
return HDC(ret)
}
func SelectObject(hdc HDC, hgdiobj HGDIOBJ) HGDIOBJ {
ret, _, _ := procSelectObject.Call(
uintptr(hdc),
uintptr(hgdiobj))
if ret == 0 {
panic("SelectObject failed")
}
return HGDIOBJ(ret)
}
func SetBkMode(hdc HDC, iBkMode int) int {
ret, _, _ := procSetBkMode.Call(
uintptr(hdc),
uintptr(iBkMode))
if ret == 0 {
panic("SetBkMode failed")
}
return int(ret)
}
func SetBrushOrgEx(hdc HDC, nXOrg, nYOrg int, lppt *POINT) bool {
ret, _, _ := procSetBrushOrgEx.Call(
uintptr(hdc),
uintptr(nXOrg),
uintptr(nYOrg),
uintptr(unsafe.Pointer(lppt)))
return ret != 0
}
func SetStretchBltMode(hdc HDC, iStretchMode int) int {
ret, _, _ := procSetStretchBltMode.Call(
uintptr(hdc),
uintptr(iStretchMode))
return int(ret)
}
func SetTextColor(hdc HDC, crColor COLORREF) COLORREF {
ret, _, _ := procSetTextColor.Call(
uintptr(hdc),
uintptr(crColor))
if ret == CLR_INVALID {
panic("SetTextColor failed")
}
return COLORREF(ret)
}
func SetBkColor(hdc HDC, crColor COLORREF) COLORREF {
ret, _, _ := procSetBkColor.Call(
uintptr(hdc),
uintptr(crColor))
if ret == CLR_INVALID {
panic("SetBkColor failed")
}
return COLORREF(ret)
}
func StartDoc(hdc HDC, lpdi *DOCINFO) int {
ret, _, _ := procStartDoc.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(lpdi)))
return int(ret)
}
func StartPage(hdc HDC) int {
ret, _, _ := procStartPage.Call(
uintptr(hdc))
return int(ret)
}
func StretchBlt(hdcDest HDC, nXOriginDest, nYOriginDest, nWidthDest, nHeightDest int, hdcSrc HDC, nXOriginSrc, nYOriginSrc, nWidthSrc, nHeightSrc int, dwRop uint) {
ret, _, _ := procStretchBlt.Call(
uintptr(hdcDest),
uintptr(nXOriginDest),
uintptr(nYOriginDest),
uintptr(nWidthDest),
uintptr(nHeightDest),
uintptr(hdcSrc),
uintptr(nXOriginSrc),
uintptr(nYOriginSrc),
uintptr(nWidthSrc),
uintptr(nHeightSrc),
uintptr(dwRop))
if ret == 0 {
panic("StretchBlt failed")
}
}
func SetDIBitsToDevice(hdc HDC, xDest, yDest, dwWidth, dwHeight, xSrc, ySrc int, uStartScan, cScanLines uint, lpvBits []byte, lpbmi *BITMAPINFO, fuColorUse uint) int {
ret, _, _ := procSetDIBitsToDevice.Call(
uintptr(hdc),
uintptr(xDest),
uintptr(yDest),
uintptr(dwWidth),
uintptr(dwHeight),
uintptr(xSrc),
uintptr(ySrc),
uintptr(uStartScan),
uintptr(cScanLines),
uintptr(unsafe.Pointer(&lpvBits[0])),
uintptr(unsafe.Pointer(lpbmi)),
uintptr(fuColorUse))
return int(ret)
}
func ChoosePixelFormat(hdc HDC, pfd *PIXELFORMATDESCRIPTOR) int {
ret, _, _ := procChoosePixelFormat.Call(
uintptr(hdc),
uintptr(unsafe.Pointer(pfd)),
)
return int(ret)
}
func DescribePixelFormat(hdc HDC, iPixelFormat int, nBytes uint, pfd *PIXELFORMATDESCRIPTOR) int {
ret, _, _ := procDescribePixelFormat.Call(
uintptr(hdc),
uintptr(iPixelFormat),
uintptr(nBytes),
uintptr(unsafe.Pointer(pfd)),
)
return int(ret)
}
func GetEnhMetaFilePixelFormat(hemf HENHMETAFILE, cbBuffer uint32, pfd *PIXELFORMATDESCRIPTOR) uint {
ret, _, _ := procGetEnhMetaFilePixelFormat.Call(
uintptr(hemf),
uintptr(cbBuffer),
uintptr(unsafe.Pointer(pfd)),
)
return uint(ret)
}
func GetPixelFormat(hdc HDC) int {
ret, _, _ := procGetPixelFormat.Call(
uintptr(hdc),
)
return int(ret)
}
func SetPixelFormat(hdc HDC, iPixelFormat int, pfd *PIXELFORMATDESCRIPTOR) bool {
ret, _, _ := procSetPixelFormat.Call(
uintptr(hdc),
uintptr(iPixelFormat),
uintptr(unsafe.Pointer(pfd)),
)
return ret == TRUE
}
func SwapBuffers(hdc HDC) bool {
ret, _, _ := procSwapBuffers.Call(uintptr(hdc))
return ret == TRUE
}

177
v3/pkg/w32/gdiplus.go Normal file
View File

@ -0,0 +1,177 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"errors"
"fmt"
"syscall"
"unsafe"
)
const (
Ok = 0
GenericError = 1
InvalidParameter = 2
OutOfMemory = 3
ObjectBusy = 4
InsufficientBuffer = 5
NotImplemented = 6
Win32Error = 7
WrongState = 8
Aborted = 9
FileNotFound = 10
ValueOverflow = 11
AccessDenied = 12
UnknownImageFormat = 13
FontFamilyNotFound = 14
FontStyleNotFound = 15
NotTrueTypeFont = 16
UnsupportedGdiplusVersion = 17
GdiplusNotInitialized = 18
PropertyNotFound = 19
PropertyNotSupported = 20
ProfileNotFound = 21
)
func GetGpStatus(s int32) string {
switch s {
case Ok:
return "Ok"
case GenericError:
return "GenericError"
case InvalidParameter:
return "InvalidParameter"
case OutOfMemory:
return "OutOfMemory"
case ObjectBusy:
return "ObjectBusy"
case InsufficientBuffer:
return "InsufficientBuffer"
case NotImplemented:
return "NotImplemented"
case Win32Error:
return "Win32Error"
case WrongState:
return "WrongState"
case Aborted:
return "Aborted"
case FileNotFound:
return "FileNotFound"
case ValueOverflow:
return "ValueOverflow"
case AccessDenied:
return "AccessDenied"
case UnknownImageFormat:
return "UnknownImageFormat"
case FontFamilyNotFound:
return "FontFamilyNotFound"
case FontStyleNotFound:
return "FontStyleNotFound"
case NotTrueTypeFont:
return "NotTrueTypeFont"
case UnsupportedGdiplusVersion:
return "UnsupportedGdiplusVersion"
case GdiplusNotInitialized:
return "GdiplusNotInitialized"
case PropertyNotFound:
return "PropertyNotFound"
case PropertyNotSupported:
return "PropertyNotSupported"
case ProfileNotFound:
return "ProfileNotFound"
}
return "Unknown Status Value"
}
var (
token uintptr
modgdiplus = syscall.NewLazyDLL("gdiplus.dll")
procGdipCreateBitmapFromFile = modgdiplus.NewProc("GdipCreateBitmapFromFile")
procGdipCreateBitmapFromHBITMAP = modgdiplus.NewProc("GdipCreateBitmapFromHBITMAP")
procGdipCreateHBITMAPFromBitmap = modgdiplus.NewProc("GdipCreateHBITMAPFromBitmap")
procGdipCreateBitmapFromResource = modgdiplus.NewProc("GdipCreateBitmapFromResource")
procGdipCreateBitmapFromStream = modgdiplus.NewProc("GdipCreateBitmapFromStream")
procGdipDisposeImage = modgdiplus.NewProc("GdipDisposeImage")
procGdiplusShutdown = modgdiplus.NewProc("GdiplusShutdown")
procGdiplusStartup = modgdiplus.NewProc("GdiplusStartup")
)
func GdipCreateBitmapFromFile(filename string) (*uintptr, error) {
var bitmap *uintptr
ret, _, _ := procGdipCreateBitmapFromFile.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(filename))),
uintptr(unsafe.Pointer(&bitmap)))
if ret != Ok {
return nil, errors.New(fmt.Sprintf("GdipCreateBitmapFromFile failed with status '%s' for file '%s'", GetGpStatus(int32(ret)), filename))
}
return bitmap, nil
}
func GdipCreateBitmapFromResource(instance HINSTANCE, resId *uint16) (*uintptr, error) {
var bitmap *uintptr
ret, _, _ := procGdipCreateBitmapFromResource.Call(
uintptr(instance),
uintptr(unsafe.Pointer(resId)),
uintptr(unsafe.Pointer(&bitmap)))
if ret != Ok {
return nil, errors.New(fmt.Sprintf("GdiCreateBitmapFromResource failed with status '%s'", GetGpStatus(int32(ret))))
}
return bitmap, nil
}
func GdipCreateBitmapFromStream(stream *IStream) (*uintptr, error) {
var bitmap *uintptr
ret, _, _ := procGdipCreateBitmapFromStream.Call(
uintptr(unsafe.Pointer(stream)),
uintptr(unsafe.Pointer(&bitmap)))
if ret != Ok {
return nil, errors.New(fmt.Sprintf("GdipCreateBitmapFromStream failed with status '%s'", GetGpStatus(int32(ret))))
}
return bitmap, nil
}
func GdipCreateHBITMAPFromBitmap(bitmap *uintptr, background uint32) (HBITMAP, error) {
var hbitmap HBITMAP
ret, _, _ := procGdipCreateHBITMAPFromBitmap.Call(
uintptr(unsafe.Pointer(bitmap)),
uintptr(unsafe.Pointer(&hbitmap)),
uintptr(background))
if ret != Ok {
return 0, errors.New(fmt.Sprintf("GdipCreateHBITMAPFromBitmap failed with status '%s'", GetGpStatus(int32(ret))))
}
return hbitmap, nil
}
func GdipDisposeImage(image *uintptr) {
procGdipDisposeImage.Call(uintptr(unsafe.Pointer(image)))
}
func GdiplusShutdown() {
procGdiplusShutdown.Call(token)
}
func GdiplusStartup(input *GdiplusStartupInput, output *GdiplusStartupOutput) {
ret, _, _ := procGdiplusStartup.Call(
uintptr(unsafe.Pointer(&token)),
uintptr(unsafe.Pointer(input)),
uintptr(unsafe.Pointer(output)))
if ret != Ok {
panic("GdiplusStartup failed with status " + GetGpStatus(int32(ret)))
}
}

45
v3/pkg/w32/idispatch.go Normal file
View File

@ -0,0 +1,45 @@
//go:build windows
/*
* Copyright (C) 2019 The Winc Authors. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"unsafe"
)
type pIDispatchVtbl struct {
pQueryInterface uintptr
pAddRef uintptr
pRelease uintptr
pGetTypeInfoCount uintptr
pGetTypeInfo uintptr
pGetIDsOfNames uintptr
pInvoke uintptr
}
type IDispatch struct {
lpVtbl *pIDispatchVtbl
}
func (this *IDispatch) QueryInterface(id *GUID) *IDispatch {
return ComQueryInterface((*IUnknown)(unsafe.Pointer(this)), id)
}
func (this *IDispatch) AddRef() int32 {
return ComAddRef((*IUnknown)(unsafe.Pointer(this)))
}
func (this *IDispatch) Release() int32 {
return ComRelease((*IUnknown)(unsafe.Pointer(this)))
}
func (this *IDispatch) GetIDsOfName(names []string) []int32 {
return ComGetIDsOfName(this, names)
}
func (this *IDispatch) Invoke(dispid int32, dispatch int16, params ...interface{}) *VARIANT {
return ComInvoke(this, dispid, dispatch, params...)
}

33
v3/pkg/w32/istream.go Normal file
View File

@ -0,0 +1,33 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"unsafe"
)
type pIStreamVtbl struct {
pQueryInterface uintptr
pAddRef uintptr
pRelease uintptr
}
type IStream struct {
lpVtbl *pIStreamVtbl
}
func (this *IStream) QueryInterface(id *GUID) *IDispatch {
return ComQueryInterface((*IUnknown)(unsafe.Pointer(this)), id)
}
func (this *IStream) AddRef() int32 {
return ComAddRef((*IUnknown)(unsafe.Pointer(this)))
}
func (this *IStream) Release() int32 {
return ComRelease((*IUnknown)(unsafe.Pointer(this)))
}

29
v3/pkg/w32/iunknown.go Normal file
View File

@ -0,0 +1,29 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
type pIUnknownVtbl struct {
pQueryInterface uintptr
pAddRef uintptr
pRelease uintptr
}
type IUnknown struct {
lpVtbl *pIUnknownVtbl
}
func (this *IUnknown) QueryInterface(id *GUID) *IDispatch {
return ComQueryInterface(this, id)
}
func (this *IUnknown) AddRef() int32 {
return ComAddRef(this)
}
func (this *IUnknown) Release() int32 {
return ComRelease(this)
}

332
v3/pkg/w32/kernel32.go Normal file
View File

@ -0,0 +1,332 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetModuleHandle = modkernel32.NewProc("GetModuleHandleW")
procMulDiv = modkernel32.NewProc("MulDiv")
procGetConsoleWindow = modkernel32.NewProc("GetConsoleWindow")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procGetCurrentThreadId = modkernel32.NewProc("GetCurrentThreadId")
procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives")
procGetLogicalDriveStrings = modkernel32.NewProc("GetLogicalDriveStringsW")
procGetUserDefaultLCID = modkernel32.NewProc("GetUserDefaultLCID")
procLstrlen = modkernel32.NewProc("lstrlenW")
procLstrcpy = modkernel32.NewProc("lstrcpyW")
procGlobalAlloc = modkernel32.NewProc("GlobalAlloc")
procGlobalFree = modkernel32.NewProc("GlobalFree")
procGlobalLock = modkernel32.NewProc("GlobalLock")
procGlobalUnlock = modkernel32.NewProc("GlobalUnlock")
procMoveMemory = modkernel32.NewProc("RtlMoveMemory")
procFindResource = modkernel32.NewProc("FindResourceW")
procSizeofResource = modkernel32.NewProc("SizeofResource")
procLockResource = modkernel32.NewProc("LockResource")
procLoadResource = modkernel32.NewProc("LoadResource")
procGetLastError = modkernel32.NewProc("GetLastError")
procOpenProcess = modkernel32.NewProc("OpenProcess")
procTerminateProcess = modkernel32.NewProc("TerminateProcess")
procCloseHandle = modkernel32.NewProc("CloseHandle")
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
procModule32First = modkernel32.NewProc("Module32FirstW")
procModule32Next = modkernel32.NewProc("Module32NextW")
procGetSystemTimes = modkernel32.NewProc("GetSystemTimes")
procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo")
procSetConsoleTextAttribute = modkernel32.NewProc("SetConsoleTextAttribute")
procGetDiskFreeSpaceEx = modkernel32.NewProc("GetDiskFreeSpaceExW")
procGetProcessTimes = modkernel32.NewProc("GetProcessTimes")
procSetSystemTime = modkernel32.NewProc("SetSystemTime")
procGetSystemTime = modkernel32.NewProc("GetSystemTime")
)
func GetModuleHandle(modulename string) HINSTANCE {
var mn uintptr
if modulename == "" {
mn = 0
} else {
mn = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(modulename)))
}
ret, _, _ := procGetModuleHandle.Call(mn)
return HINSTANCE(ret)
}
func MulDiv(number, numerator, denominator int) int {
ret, _, _ := procMulDiv.Call(
uintptr(number),
uintptr(numerator),
uintptr(denominator))
return int(ret)
}
func GetConsoleWindow() HWND {
ret, _, _ := procGetConsoleWindow.Call()
return HWND(ret)
}
func GetCurrentThread() HANDLE {
ret, _, _ := procGetCurrentThread.Call()
return HANDLE(ret)
}
func GetCurrentThreadId() HANDLE {
ret, _, _ := procGetCurrentThreadId.Call()
return HANDLE(ret)
}
func GetLogicalDrives() uint32 {
ret, _, _ := procGetLogicalDrives.Call()
return uint32(ret)
}
func GetUserDefaultLCID() uint32 {
ret, _, _ := procGetUserDefaultLCID.Call()
return uint32(ret)
}
func Lstrlen(lpString *uint16) int {
ret, _, _ := procLstrlen.Call(uintptr(unsafe.Pointer(lpString)))
return int(ret)
}
func Lstrcpy(buf []uint16, lpString *uint16) {
procLstrcpy.Call(
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(lpString)))
}
func GlobalAlloc(uFlags uint, dwBytes uint32) HGLOBAL {
ret, _, _ := procGlobalAlloc.Call(
uintptr(uFlags),
uintptr(dwBytes))
if ret == 0 {
panic("GlobalAlloc failed")
}
return HGLOBAL(ret)
}
func GlobalFree(hMem HGLOBAL) {
ret, _, _ := procGlobalFree.Call(uintptr(hMem))
if ret != 0 {
panic("GlobalFree failed")
}
}
func GlobalLock(hMem HGLOBAL) unsafe.Pointer {
ret, _, _ := procGlobalLock.Call(uintptr(hMem))
if ret == 0 {
panic("GlobalLock failed")
}
return unsafe.Pointer(ret)
}
func GlobalUnlock(hMem HGLOBAL) bool {
ret, _, _ := procGlobalUnlock.Call(uintptr(hMem))
return ret != 0
}
func MoveMemory(destination, source unsafe.Pointer, length uint32) {
procMoveMemory.Call(
uintptr(unsafe.Pointer(destination)),
uintptr(source),
uintptr(length))
}
func FindResource(hModule HMODULE, lpName, lpType *uint16) (HRSRC, error) {
ret, _, _ := procFindResource.Call(
uintptr(hModule),
uintptr(unsafe.Pointer(lpName)),
uintptr(unsafe.Pointer(lpType)))
if ret == 0 {
return 0, syscall.GetLastError()
}
return HRSRC(ret), nil
}
func SizeofResource(hModule HMODULE, hResInfo HRSRC) uint32 {
ret, _, _ := procSizeofResource.Call(
uintptr(hModule),
uintptr(hResInfo))
if ret == 0 {
panic("SizeofResource failed")
}
return uint32(ret)
}
func LockResource(hResData HGLOBAL) unsafe.Pointer {
ret, _, _ := procLockResource.Call(uintptr(hResData))
if ret == 0 {
panic("LockResource failed")
}
return unsafe.Pointer(ret)
}
func LoadResource(hModule HMODULE, hResInfo HRSRC) HGLOBAL {
ret, _, _ := procLoadResource.Call(
uintptr(hModule),
uintptr(hResInfo))
if ret == 0 {
panic("LoadResource failed")
}
return HGLOBAL(ret)
}
func GetLastError() uint32 {
ret, _, _ := procGetLastError.Call()
return uint32(ret)
}
func OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) HANDLE {
inherit := 0
if inheritHandle {
inherit = 1
}
ret, _, _ := procOpenProcess.Call(
uintptr(desiredAccess),
uintptr(inherit),
uintptr(processId))
return HANDLE(ret)
}
func TerminateProcess(hProcess HANDLE, uExitCode uint) bool {
ret, _, _ := procTerminateProcess.Call(
uintptr(hProcess),
uintptr(uExitCode))
return ret != 0
}
func CloseHandle(object HANDLE) bool {
ret, _, _ := procCloseHandle.Call(
uintptr(object))
return ret != 0
}
func CreateToolhelp32Snapshot(flags, processId uint32) HANDLE {
ret, _, _ := procCreateToolhelp32Snapshot.Call(
uintptr(flags),
uintptr(processId))
if ret <= 0 {
return HANDLE(0)
}
return HANDLE(ret)
}
func Module32First(snapshot HANDLE, me *MODULEENTRY32) bool {
ret, _, _ := procModule32First.Call(
uintptr(snapshot),
uintptr(unsafe.Pointer(me)))
return ret != 0
}
func Module32Next(snapshot HANDLE, me *MODULEENTRY32) bool {
ret, _, _ := procModule32Next.Call(
uintptr(snapshot),
uintptr(unsafe.Pointer(me)))
return ret != 0
}
func GetSystemTimes(lpIdleTime, lpKernelTime, lpUserTime *FILETIME) bool {
ret, _, _ := procGetSystemTimes.Call(
uintptr(unsafe.Pointer(lpIdleTime)),
uintptr(unsafe.Pointer(lpKernelTime)),
uintptr(unsafe.Pointer(lpUserTime)))
return ret != 0
}
func GetProcessTimes(hProcess HANDLE, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime *FILETIME) bool {
ret, _, _ := procGetProcessTimes.Call(
uintptr(hProcess),
uintptr(unsafe.Pointer(lpCreationTime)),
uintptr(unsafe.Pointer(lpExitTime)),
uintptr(unsafe.Pointer(lpKernelTime)),
uintptr(unsafe.Pointer(lpUserTime)))
return ret != 0
}
func GetConsoleScreenBufferInfo(hConsoleOutput HANDLE) *CONSOLE_SCREEN_BUFFER_INFO {
var csbi CONSOLE_SCREEN_BUFFER_INFO
ret, _, _ := procGetConsoleScreenBufferInfo.Call(
uintptr(hConsoleOutput),
uintptr(unsafe.Pointer(&csbi)))
if ret == 0 {
return nil
}
return &csbi
}
func SetConsoleTextAttribute(hConsoleOutput HANDLE, wAttributes uint16) bool {
ret, _, _ := procSetConsoleTextAttribute.Call(
uintptr(hConsoleOutput),
uintptr(wAttributes))
return ret != 0
}
func GetDiskFreeSpaceEx(dirName string) (r bool,
freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64) {
ret, _, _ := procGetDiskFreeSpaceEx.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(dirName))),
uintptr(unsafe.Pointer(&freeBytesAvailable)),
uintptr(unsafe.Pointer(&totalNumberOfBytes)),
uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)))
return ret != 0,
freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes
}
func GetSystemTime() *SYSTEMTIME {
var time SYSTEMTIME
procGetSystemTime.Call(
uintptr(unsafe.Pointer(&time)))
return &time
}
func SetSystemTime(time *SYSTEMTIME) bool {
ret, _, _ := procSetSystemTime.Call(
uintptr(unsafe.Pointer(time)))
return ret != 0
}
func GetLogicalDriveStrings(nBufferLength uint32, lpBuffer *uint16) uint32 {
ret, _, _ := procGetLogicalDriveStrings.Call(
uintptr(nBufferLength),
uintptr(unsafe.Pointer(lpBuffer)),
0)
return uint32(ret)
}

65
v3/pkg/w32/ole32.go Normal file
View File

@ -0,0 +1,65 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modole32 = syscall.NewLazyDLL("ole32.dll")
procCoInitializeEx = modole32.NewProc("CoInitializeEx")
procCoInitialize = modole32.NewProc("CoInitialize")
procCoUninitialize = modole32.NewProc("CoUninitialize")
procCreateStreamOnHGlobal = modole32.NewProc("CreateStreamOnHGlobal")
)
func CoInitializeEx(coInit uintptr) HRESULT {
ret, _, _ := procCoInitializeEx.Call(
0,
coInit)
switch uint32(ret) {
case E_INVALIDARG:
panic("CoInitializeEx failed with E_INVALIDARG")
case E_OUTOFMEMORY:
panic("CoInitializeEx failed with E_OUTOFMEMORY")
case E_UNEXPECTED:
panic("CoInitializeEx failed with E_UNEXPECTED")
}
return HRESULT(ret)
}
func CoInitialize() {
procCoInitialize.Call(0)
}
func CoUninitialize() {
procCoUninitialize.Call()
}
func CreateStreamOnHGlobal(hGlobal HGLOBAL, fDeleteOnRelease bool) *IStream {
stream := new(IStream)
ret, _, _ := procCreateStreamOnHGlobal.Call(
uintptr(hGlobal),
uintptr(BoolToBOOL(fDeleteOnRelease)),
uintptr(unsafe.Pointer(&stream)))
switch uint32(ret) {
case E_INVALIDARG:
panic("CreateStreamOnHGlobal failed with E_INVALIDARG")
case E_OUTOFMEMORY:
panic("CreateStreamOnHGlobal failed with E_OUTOFMEMORY")
case E_UNEXPECTED:
panic("CreateStreamOnHGlobal failed with E_UNEXPECTED")
}
return stream
}

50
v3/pkg/w32/oleaut32.go Normal file
View File

@ -0,0 +1,50 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unsafe"
)
var (
modoleaut32 = syscall.NewLazyDLL("oleaut32")
procVariantInit = modoleaut32.NewProc("VariantInit")
procSysAllocString = modoleaut32.NewProc("SysAllocString")
procSysFreeString = modoleaut32.NewProc("SysFreeString")
procSysStringLen = modoleaut32.NewProc("SysStringLen")
procCreateDispTypeInfo = modoleaut32.NewProc("CreateDispTypeInfo")
procCreateStdDispatch = modoleaut32.NewProc("CreateStdDispatch")
)
func VariantInit(v *VARIANT) {
hr, _, _ := procVariantInit.Call(uintptr(unsafe.Pointer(v)))
if hr != 0 {
panic("Invoke VariantInit error.")
}
return
}
func SysAllocString(v string) (ss *int16) {
pss, _, _ := procSysAllocString.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v))))
ss = (*int16)(unsafe.Pointer(pss))
return
}
func SysFreeString(v *int16) {
hr, _, _ := procSysFreeString.Call(uintptr(unsafe.Pointer(v)))
if hr != 0 {
panic("Invoke SysFreeString error.")
}
return
}
func SysStringLen(v *int16) uint {
l, _, _ := procSysStringLen.Call(uintptr(unsafe.Pointer(v)))
return uint(l)
}

118
v3/pkg/w32/screen.go Normal file
View File

@ -0,0 +1,118 @@
//go:build windows
package w32
import (
"fmt"
"syscall"
"unsafe"
)
func MonitorsEqual(first MONITORINFO, second MONITORINFO) bool {
// Checks to make sure all the fields are the same.
// A cleaner way would be to check identity of devices. but I couldn't find a way of doing that using the win32 API
return first.DwFlags == second.DwFlags &&
first.RcMonitor.Top == second.RcMonitor.Top &&
first.RcMonitor.Bottom == second.RcMonitor.Bottom &&
first.RcMonitor.Right == second.RcMonitor.Right &&
first.RcMonitor.Left == second.RcMonitor.Left &&
first.RcWork.Top == second.RcWork.Top &&
first.RcWork.Bottom == second.RcWork.Bottom &&
first.RcWork.Right == second.RcWork.Right &&
first.RcWork.Left == second.RcWork.Left
}
func GetMonitorInformation(hMonitor HMONITOR) (*MONITORINFO, error) {
// Adapted from winc.utils.getMonitorInfo
// See docs for
//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
var info MONITORINFO
info.CbSize = uint32(unsafe.Sizeof(info))
succeeded := GetMonitorInfo(hMonitor, &info)
if !succeeded {
return &info, fmt.Errorf("Windows call to getMonitorInfo failed")
}
return &info, nil
}
type Screen struct {
IsCurrent bool
IsPrimary bool
Width int
Height int
}
func EnumProc(hMonitor HMONITOR, hdcMonitor HDC, lprcMonitor *RECT, screenContainer *ScreenContainer) uintptr {
// adapted from https://stackoverflow.com/a/23492886/4188138
// see docs for the following pages to better understand this function
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
ourMonitorData := Screen{}
currentMonHndl := MonitorFromWindow(screenContainer.mainWinHandle, MONITOR_DEFAULTTONEAREST)
currentMonInfo, currErr := GetMonitorInformation(currentMonHndl)
if currErr != nil {
screenContainer.errors = append(screenContainer.errors, currErr)
screenContainer.monitors = append(screenContainer.monitors, Screen{})
// not sure what the consequences of returning false are, so let's just return true and handle it ourselves
return TRUE
}
monInfo, err := GetMonitorInformation(hMonitor)
if err != nil {
screenContainer.errors = append(screenContainer.errors, err)
screenContainer.monitors = append(screenContainer.monitors, Screen{})
return TRUE
}
height := lprcMonitor.Right - lprcMonitor.Left
width := lprcMonitor.Bottom - lprcMonitor.Top
ourMonitorData.IsPrimary = monInfo.DwFlags&MONITORINFOF_PRIMARY == 1
ourMonitorData.Height = int(width)
ourMonitorData.Width = int(height)
ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo)
// the reason we need a container is that we have don't know how many times this function will be called
// this "append" call could potentially do an allocation and rewrite the pointer to monitors. So we save the pointer in screenContainer.monitors
// and retrieve the values after all EnumProc calls
// If EnumProc is multi-threaded, this could be problematic. Although, I don't think it is.
screenContainer.monitors = append(screenContainer.monitors, ourMonitorData)
// let's keep screenContainer.errors the same size as screenContainer.monitors in case we want to match them up later if necessary
screenContainer.errors = append(screenContainer.errors, nil)
return TRUE
}
type ScreenContainer struct {
monitors []Screen
errors []error
mainWinHandle HWND
}
func GetAllScreens(mainWinHandle HWND) ([]Screen, error) {
// TODO fix hack of container sharing by having a proper data sharing mechanism between windows and the runtime
monitorContainer := ScreenContainer{mainWinHandle: mainWinHandle}
returnErr := error(nil)
var errorStrings []string
dc := GetDC(0)
defer ReleaseDC(0, dc)
succeeded := EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer))
if !succeeded {
return monitorContainer.monitors, fmt.Errorf("Windows call to EnumDisplayMonitors failed")
}
for idx, err := range monitorContainer.errors {
if err != nil {
errorStrings = append(errorStrings, fmt.Sprintf("Error from monitor #%v, %v", idx+1, err))
}
}
if len(errorStrings) > 0 {
returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings)
}
return monitorContainer.monitors, returnErr
}

29
v3/pkg/w32/shcore.go Normal file
View File

@ -0,0 +1,29 @@
//go:build windows
package w32
import (
"syscall"
"unsafe"
)
var (
modshcore = syscall.NewLazyDLL("shcore.dll")
procGetDpiForMonitor = modshcore.NewProc("GetDpiForMonitor")
)
func HasGetDPIForMonitorFunc() bool {
err := procGetDpiForMonitor.Find()
return err == nil
}
func GetDPIForMonitor(hmonitor HMONITOR, dpiType MONITOR_DPI_TYPE, dpiX *UINT, dpiY *UINT) uintptr {
ret, _, _ := procGetDpiForMonitor.Call(
hmonitor,
uintptr(dpiType),
uintptr(unsafe.Pointer(dpiX)),
uintptr(unsafe.Pointer(dpiY)))
return ret
}

235
v3/pkg/w32/shell32.go Normal file
View File

@ -0,0 +1,235 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"errors"
"fmt"
"syscall"
"unsafe"
)
type CSIDL uint32
const (
CSIDL_DESKTOP = 0x00
CSIDL_INTERNET = 0x01
CSIDL_PROGRAMS = 0x02
CSIDL_CONTROLS = 0x03
CSIDL_PRINTERS = 0x04
CSIDL_PERSONAL = 0x05
CSIDL_FAVORITES = 0x06
CSIDL_STARTUP = 0x07
CSIDL_RECENT = 0x08
CSIDL_SENDTO = 0x09
CSIDL_BITBUCKET = 0x0A
CSIDL_STARTMENU = 0x0B
CSIDL_MYDOCUMENTS = 0x0C
CSIDL_MYMUSIC = 0x0D
CSIDL_MYVIDEO = 0x0E
CSIDL_DESKTOPDIRECTORY = 0x10
CSIDL_DRIVES = 0x11
CSIDL_NETWORK = 0x12
CSIDL_NETHOOD = 0x13
CSIDL_FONTS = 0x14
CSIDL_TEMPLATES = 0x15
CSIDL_COMMON_STARTMENU = 0x16
CSIDL_COMMON_PROGRAMS = 0x17
CSIDL_COMMON_STARTUP = 0x18
CSIDL_COMMON_DESKTOPDIRECTORY = 0x19
CSIDL_APPDATA = 0x1A
CSIDL_PRINTHOOD = 0x1B
CSIDL_LOCAL_APPDATA = 0x1C
CSIDL_ALTSTARTUP = 0x1D
CSIDL_COMMON_ALTSTARTUP = 0x1E
CSIDL_COMMON_FAVORITES = 0x1F
CSIDL_INTERNET_CACHE = 0x20
CSIDL_COOKIES = 0x21
CSIDL_HISTORY = 0x22
CSIDL_COMMON_APPDATA = 0x23
CSIDL_WINDOWS = 0x24
CSIDL_SYSTEM = 0x25
CSIDL_PROGRAM_FILES = 0x26
CSIDL_MYPICTURES = 0x27
CSIDL_PROFILE = 0x28
CSIDL_SYSTEMX86 = 0x29
CSIDL_PROGRAM_FILESX86 = 0x2A
CSIDL_PROGRAM_FILES_COMMON = 0x2B
CSIDL_PROGRAM_FILES_COMMONX86 = 0x2C
CSIDL_COMMON_TEMPLATES = 0x2D
CSIDL_COMMON_DOCUMENTS = 0x2E
CSIDL_COMMON_ADMINTOOLS = 0x2F
CSIDL_ADMINTOOLS = 0x30
CSIDL_CONNECTIONS = 0x31
CSIDL_COMMON_MUSIC = 0x35
CSIDL_COMMON_PICTURES = 0x36
CSIDL_COMMON_VIDEO = 0x37
CSIDL_RESOURCES = 0x38
CSIDL_RESOURCES_LOCALIZED = 0x39
CSIDL_COMMON_OEM_LINKS = 0x3A
CSIDL_CDBURN_AREA = 0x3B
CSIDL_COMPUTERSNEARME = 0x3D
CSIDL_FLAG_CREATE = 0x8000
CSIDL_FLAG_DONT_VERIFY = 0x4000
CSIDL_FLAG_NO_ALIAS = 0x1000
CSIDL_FLAG_PER_USER_INIT = 0x8000
CSIDL_FLAG_MASK = 0xFF00
)
var (
modshell32 = syscall.NewLazyDLL("shell32.dll")
procSHBrowseForFolder = modshell32.NewProc("SHBrowseForFolderW")
procSHGetPathFromIDList = modshell32.NewProc("SHGetPathFromIDListW")
procDragAcceptFiles = modshell32.NewProc("DragAcceptFiles")
procDragQueryFile = modshell32.NewProc("DragQueryFileW")
procDragQueryPoint = modshell32.NewProc("DragQueryPoint")
procDragFinish = modshell32.NewProc("DragFinish")
procShellExecute = modshell32.NewProc("ShellExecuteW")
procExtractIcon = modshell32.NewProc("ExtractIconW")
procGetSpecialFolderPath = modshell32.NewProc("SHGetSpecialFolderPathW")
)
func SHBrowseForFolder(bi *BROWSEINFO) uintptr {
ret, _, _ := procSHBrowseForFolder.Call(uintptr(unsafe.Pointer(bi)))
return ret
}
func SHGetPathFromIDList(idl uintptr) string {
buf := make([]uint16, 1024)
procSHGetPathFromIDList.Call(
idl,
uintptr(unsafe.Pointer(&buf[0])))
return syscall.UTF16ToString(buf)
}
func DragAcceptFiles(hwnd HWND, accept bool) {
procDragAcceptFiles.Call(
uintptr(hwnd),
uintptr(BoolToBOOL(accept)))
}
func DragQueryFile(hDrop HDROP, iFile uint) (fileName string, fileCount uint) {
ret, _, _ := procDragQueryFile.Call(
uintptr(hDrop),
uintptr(iFile),
0,
0)
fileCount = uint(ret)
if iFile != 0xFFFFFFFF {
buf := make([]uint16, fileCount+1)
ret, _, _ := procDragQueryFile.Call(
uintptr(hDrop),
uintptr(iFile),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(fileCount+1))
if ret == 0 {
panic("Invoke DragQueryFile error.")
}
fileName = syscall.UTF16ToString(buf)
}
return
}
func DragQueryPoint(hDrop HDROP) (x, y int, isClientArea bool) {
var pt POINT
ret, _, _ := procDragQueryPoint.Call(
uintptr(hDrop),
uintptr(unsafe.Pointer(&pt)))
return int(pt.X), int(pt.Y), (ret == 1)
}
func DragFinish(hDrop HDROP) {
procDragFinish.Call(uintptr(hDrop))
}
func ShellExecute(hwnd HWND, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
var op, param, directory uintptr
if len(lpOperation) != 0 {
op = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
}
if len(lpParameters) != 0 {
param = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters)))
}
if len(lpDirectory) != 0 {
directory = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
}
ret, _, _ := procShellExecute.Call(
uintptr(hwnd),
op,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
param,
directory,
uintptr(nShowCmd))
errorMsg := ""
if ret != 0 && ret <= 32 {
switch int(ret) {
case ERROR_FILE_NOT_FOUND:
errorMsg = "The specified file was not found."
case ERROR_PATH_NOT_FOUND:
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. This error will also be returned if you attempt to print a file that is not printable."
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", ret)
}
} else {
return nil
}
return errors.New(errorMsg)
}
func ExtractIcon(lpszExeFileName string, nIconIndex int) HICON {
ret, _, _ := procExtractIcon.Call(
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpszExeFileName))),
uintptr(nIconIndex))
return HICON(ret)
}
func SHGetSpecialFolderPath(hwndOwner HWND, lpszPath *uint16, csidl CSIDL, fCreate bool) bool {
ret, _, _ := procGetSpecialFolderPath.Call(
uintptr(hwndOwner),
uintptr(unsafe.Pointer(lpszPath)),
uintptr(csidl),
uintptr(BoolToBOOL(fCreate)),
0,
0)
return ret != 0
}

26
v3/pkg/w32/shlwapi.go Normal file
View File

@ -0,0 +1,26 @@
//go:build windows
package w32
import (
"syscall"
"unsafe"
)
var (
modshlwapi = syscall.NewLazyDLL("shlwapi.dll")
procSHCreateMemStream = modshlwapi.NewProc("SHCreateMemStream")
)
func SHCreateMemStream(data []byte) (uintptr, error) {
ret, _, err := procSHCreateMemStream.Call(
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
)
if ret == 0 {
return 0, err
}
return ret, nil
}

119
v3/pkg/w32/theme.go Normal file
View File

@ -0,0 +1,119 @@
//go:build windows
package w32
import (
"unsafe"
"golang.org/x/sys/windows/registry"
)
type DWMWINDOWATTRIBUTE int32
const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19
const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20
const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34
const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35
const DwmwaTextColor DWMWINDOWATTRIBUTE = 36
const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38
const SPI_GETHIGHCONTRAST = 0x0042
const HCF_HIGHCONTRASTON = 0x00000001
// BackdropType defines the type of translucency we wish to use
type BackdropType int32
func dwmSetWindowAttribute(hwnd uintptr, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) {
ret, _, err := procDwmSetWindowAttribute.Call(
hwnd,
uintptr(dwAttribute),
uintptr(pvAttribute),
cbAttribute)
if ret != 0 {
_ = err
// println(err.Error())
}
}
func SupportsThemes() bool {
// We can't support Windows versions before 17763
return IsWindowsVersionAtLeast(10, 0, 17763)
}
func SupportsCustomThemes() bool {
return IsWindowsVersionAtLeast(10, 0, 17763)
}
func SupportsBackdropTypes() bool {
return IsWindowsVersionAtLeast(10, 0, 22621)
}
func SupportsImmersiveDarkMode() bool {
return IsWindowsVersionAtLeast(10, 0, 18985)
}
func SetTheme(hwnd uintptr, useDarkMode bool) {
if SupportsThemes() {
attr := DwmwaUseImmersiveDarkModeBefore20h1
if SupportsImmersiveDarkMode() {
attr = DwmwaUseImmersiveDarkMode
}
var winDark int32
if useDarkMode {
winDark = 1
}
dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark))
}
}
func EnableTranslucency(hwnd uintptr, backdrop BackdropType) {
if SupportsBackdropTypes() {
dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop))
} else {
println("Warning: Translucency type unavailable on Windows < 22621")
}
}
func SetTitleBarColour(hwnd uintptr, titleBarColour int32) {
dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour))
}
func SetTitleTextColour(hwnd uintptr, titleTextColour int32) {
dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour))
}
func SetBorderColour(hwnd uintptr, titleBorderColour int32) {
dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour))
}
func IsCurrentlyDarkMode() bool {
key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer key.Close()
AppsUseLightTheme, _, err := key.GetIntegerValue("AppsUseLightTheme")
if err != nil {
return false
}
return AppsUseLightTheme == 0
}
type highContrast struct {
CbSize uint32
DwFlags uint32
LpszDefaultScheme *int16
}
func IsCurrentlyHighContrastMode() bool {
var result highContrast
result.CbSize = uint32(unsafe.Sizeof(result))
res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0)
if res == 0 {
_ = err
return false
}
r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON
return r
}

216
v3/pkg/w32/toolbar.go Normal file
View File

@ -0,0 +1,216 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
// ToolBar messages
const (
TB_ENABLEBUTTON = WM_USER + 1
TB_CHECKBUTTON = WM_USER + 2
TB_PRESSBUTTON = WM_USER + 3
TB_HIDEBUTTON = WM_USER + 4
TB_INDETERMINATE = WM_USER + 5
TB_MARKBUTTON = WM_USER + 6
TB_ISBUTTONENABLED = WM_USER + 9
TB_ISBUTTONCHECKED = WM_USER + 10
TB_ISBUTTONPRESSED = WM_USER + 11
TB_ISBUTTONHIDDEN = WM_USER + 12
TB_ISBUTTONINDETERMINATE = WM_USER + 13
TB_ISBUTTONHIGHLIGHTED = WM_USER + 14
TB_SETSTATE = WM_USER + 17
TB_GETSTATE = WM_USER + 18
TB_ADDBITMAP = WM_USER + 19
TB_DELETEBUTTON = WM_USER + 22
TB_GETBUTTON = WM_USER + 23
TB_BUTTONCOUNT = WM_USER + 24
TB_COMMANDTOINDEX = WM_USER + 25
TB_SAVERESTORE = WM_USER + 76
TB_CUSTOMIZE = WM_USER + 27
TB_ADDSTRING = WM_USER + 77
TB_GETITEMRECT = WM_USER + 29
TB_BUTTONSTRUCTSIZE = WM_USER + 30
TB_SETBUTTONSIZE = WM_USER + 31
TB_SETBITMAPSIZE = WM_USER + 32
TB_AUTOSIZE = WM_USER + 33
TB_GETTOOLTIPS = WM_USER + 35
TB_SETTOOLTIPS = WM_USER + 36
TB_SETPARENT = WM_USER + 37
TB_SETROWS = WM_USER + 39
TB_GETROWS = WM_USER + 40
TB_GETBITMAPFLAGS = WM_USER + 41
TB_SETCMDID = WM_USER + 42
TB_CHANGEBITMAP = WM_USER + 43
TB_GETBITMAP = WM_USER + 44
TB_GETBUTTONTEXT = WM_USER + 75
TB_REPLACEBITMAP = WM_USER + 46
TB_GETBUTTONSIZE = WM_USER + 58
TB_SETBUTTONWIDTH = WM_USER + 59
TB_SETINDENT = WM_USER + 47
TB_SETIMAGELIST = WM_USER + 48
TB_GETIMAGELIST = WM_USER + 49
TB_LOADIMAGES = WM_USER + 50
TB_GETRECT = WM_USER + 51
TB_SETHOTIMAGELIST = WM_USER + 52
TB_GETHOTIMAGELIST = WM_USER + 53
TB_SETDISABLEDIMAGELIST = WM_USER + 54
TB_GETDISABLEDIMAGELIST = WM_USER + 55
TB_SETSTYLE = WM_USER + 56
TB_GETSTYLE = WM_USER + 57
TB_SETMAXTEXTROWS = WM_USER + 60
TB_GETTEXTROWS = WM_USER + 61
TB_GETOBJECT = WM_USER + 62
TB_GETBUTTONINFO = WM_USER + 63
TB_SETBUTTONINFO = WM_USER + 64
TB_INSERTBUTTON = WM_USER + 67
TB_ADDBUTTONS = WM_USER + 68
TB_HITTEST = WM_USER + 69
TB_SETDRAWTEXTFLAGS = WM_USER + 70
TB_GETHOTITEM = WM_USER + 71
TB_SETHOTITEM = WM_USER + 72
TB_SETANCHORHIGHLIGHT = WM_USER + 73
TB_GETANCHORHIGHLIGHT = WM_USER + 74
TB_GETINSERTMARK = WM_USER + 79
TB_SETINSERTMARK = WM_USER + 80
TB_INSERTMARKHITTEST = WM_USER + 81
TB_MOVEBUTTON = WM_USER + 82
TB_GETMAXSIZE = WM_USER + 83
TB_SETEXTENDEDSTYLE = WM_USER + 84
TB_GETEXTENDEDSTYLE = WM_USER + 85
TB_GETPADDING = WM_USER + 86
TB_SETPADDING = WM_USER + 87
TB_SETINSERTMARKCOLOR = WM_USER + 88
TB_GETINSERTMARKCOLOR = WM_USER + 89
TB_MAPACCELERATOR = WM_USER + 90
TB_GETSTRING = WM_USER + 91
TB_SETCOLORSCHEME = CCM_SETCOLORSCHEME
TB_GETCOLORSCHEME = CCM_GETCOLORSCHEME
TB_SETUNICODEFORMAT = CCM_SETUNICODEFORMAT
TB_GETUNICODEFORMAT = CCM_GETUNICODEFORMAT
)
// ToolBar notifications
const (
TBN_FIRST = -700
TBN_DROPDOWN = TBN_FIRST - 10
)
// TBN_DROPDOWN return codes
const (
TBDDRET_DEFAULT = 0
TBDDRET_NODEFAULT = 1
TBDDRET_TREATPRESSED = 2
)
// ToolBar state constants
const (
TBSTATE_CHECKED = 1
TBSTATE_PRESSED = 2
TBSTATE_ENABLED = 4
TBSTATE_HIDDEN = 8
TBSTATE_INDETERMINATE = 16
TBSTATE_WRAP = 32
TBSTATE_ELLIPSES = 0x40
TBSTATE_MARKED = 0x0080
)
// ToolBar style constants
const (
TBSTYLE_BUTTON = 0
TBSTYLE_SEP = 1
TBSTYLE_CHECK = 2
TBSTYLE_GROUP = 4
TBSTYLE_CHECKGROUP = TBSTYLE_GROUP | TBSTYLE_CHECK
TBSTYLE_DROPDOWN = 8
TBSTYLE_AUTOSIZE = 16
TBSTYLE_NOPREFIX = 32
TBSTYLE_TOOLTIPS = 256
TBSTYLE_WRAPABLE = 512
TBSTYLE_ALTDRAG = 1024
TBSTYLE_FLAT = 2048
TBSTYLE_LIST = 4096
TBSTYLE_CUSTOMERASE = 8192
TBSTYLE_REGISTERDROP = 0x4000
TBSTYLE_TRANSPARENT = 0x8000
)
// ToolBar extended style constants
const (
TBSTYLE_EX_DRAWDDARROWS = 0x00000001
TBSTYLE_EX_MIXEDBUTTONS = 8
TBSTYLE_EX_HIDECLIPPEDBUTTONS = 16
TBSTYLE_EX_DOUBLEBUFFER = 0x80
)
// ToolBar button style constants
const (
BTNS_BUTTON = TBSTYLE_BUTTON
BTNS_SEP = TBSTYLE_SEP
BTNS_CHECK = TBSTYLE_CHECK
BTNS_GROUP = TBSTYLE_GROUP
BTNS_CHECKGROUP = TBSTYLE_CHECKGROUP
BTNS_DROPDOWN = TBSTYLE_DROPDOWN
BTNS_AUTOSIZE = TBSTYLE_AUTOSIZE
BTNS_NOPREFIX = TBSTYLE_NOPREFIX
BTNS_WHOLEDROPDOWN = 0x0080
BTNS_SHOWTEXT = 0x0040
)
// TBBUTTONINFO mask flags
const (
TBIF_IMAGE = 0x00000001
TBIF_TEXT = 0x00000002
TBIF_STATE = 0x00000004
TBIF_STYLE = 0x00000008
TBIF_LPARAM = 0x00000010
TBIF_COMMAND = 0x00000020
TBIF_SIZE = 0x00000040
TBIF_BYINDEX = 0x80000000
)
type NMMOUSE struct {
Hdr NMHDR
DwItemSpec uintptr
DwItemData uintptr
Pt POINT
DwHitInfo uintptr
}
type NMTOOLBAR struct {
Hdr NMHDR
IItem int32
TbButton TBBUTTON
CchText int32
PszText *uint16
RcButton RECT
}
type TBBUTTON struct {
IBitmap int32
IdCommand int32
FsState byte
FsStyle byte
//#ifdef _WIN64
// BYTE bReserved[6] // padding for alignment
//#elif defined(_WIN32)
BReserved [2]byte // padding for alignment
//#endif
DwData uintptr
IString uintptr
}
type TBBUTTONINFO struct {
CbSize uint32
DwMask uint32
IdCommand int32
IImage int32
FsState byte
FsStyle byte
Cx uint16
LParam uintptr
PszText uintptr
CchText int32
}

1081
v3/pkg/w32/typedef.go Normal file

File diff suppressed because it is too large Load Diff

1313
v3/pkg/w32/user32.go Normal file

File diff suppressed because it is too large Load Diff

230
v3/pkg/w32/utils.go Normal file
View File

@ -0,0 +1,230 @@
//go:build windows
/*
* Copyright (C) 2019 Tad Vizbaras. All Rights Reserved.
* Copyright (C) 2010-2012 The W32 Authors. All Rights Reserved.
*/
package w32
import (
"syscall"
"unicode/utf16"
"unsafe"
)
func MustLoadLibrary(name string) uintptr {
lib, err := syscall.LoadLibrary(name)
if err != nil {
panic(err)
}
return uintptr(lib)
}
func MustGetProcAddress(lib uintptr, name string) uintptr {
addr, err := syscall.GetProcAddress(syscall.Handle(lib), name)
if err != nil {
panic(err)
}
return uintptr(addr)
}
func SUCCEEDED(hr HRESULT) bool {
return hr >= 0
}
func FAILED(hr HRESULT) bool {
return hr < 0
}
func LOWORD(dw uint32) uint16 {
return uint16(dw)
}
func HIWORD(dw uint32) uint16 {
return uint16(dw >> 16 & 0xffff)
}
func MAKELONG(lo, hi uint16) uint32 {
return uint32(uint32(lo) | ((uint32(hi)) << 16))
}
func BoolToBOOL(value bool) BOOL {
if value {
return 1
}
return 0
}
func UTF16PtrToString(cstr *uint16) string {
if cstr != nil {
us := make([]uint16, 0, 256)
for p := uintptr(unsafe.Pointer(cstr)); ; p += 2 {
u := *(*uint16)(unsafe.Pointer(p))
if u == 0 {
return string(utf16.Decode(us))
}
us = append(us, u)
}
}
return ""
}
func ComAddRef(unknown *IUnknown) int32 {
ret, _, _ := syscall.Syscall(unknown.lpVtbl.pAddRef, 1,
uintptr(unsafe.Pointer(unknown)),
0,
0)
return int32(ret)
}
func ComRelease(unknown *IUnknown) int32 {
ret, _, _ := syscall.Syscall(unknown.lpVtbl.pRelease, 1,
uintptr(unsafe.Pointer(unknown)),
0,
0)
return int32(ret)
}
func ComQueryInterface(unknown *IUnknown, id *GUID) *IDispatch {
var disp *IDispatch
hr, _, _ := syscall.Syscall(unknown.lpVtbl.pQueryInterface, 3,
uintptr(unsafe.Pointer(unknown)),
uintptr(unsafe.Pointer(id)),
uintptr(unsafe.Pointer(&disp)))
if hr != 0 {
panic("Invoke QieryInterface error.")
}
return disp
}
func ComGetIDsOfName(disp *IDispatch, names []string) []int32 {
wnames := make([]*uint16, len(names))
dispid := make([]int32, len(names))
for i := 0; i < len(names); i++ {
wnames[i] = syscall.StringToUTF16Ptr(names[i])
}
hr, _, _ := syscall.Syscall6(disp.lpVtbl.pGetIDsOfNames, 6,
uintptr(unsafe.Pointer(disp)),
uintptr(unsafe.Pointer(IID_NULL)),
uintptr(unsafe.Pointer(&wnames[0])),
uintptr(len(names)),
uintptr(GetUserDefaultLCID()),
uintptr(unsafe.Pointer(&dispid[0])))
if hr != 0 {
panic("Invoke GetIDsOfName error.")
}
return dispid
}
func ComInvoke(disp *IDispatch, dispid int32, dispatch int16, params ...interface{}) (result *VARIANT) {
var dispparams DISPPARAMS
if dispatch&DISPATCH_PROPERTYPUT != 0 {
dispnames := [1]int32{DISPID_PROPERTYPUT}
dispparams.RgdispidNamedArgs = uintptr(unsafe.Pointer(&dispnames[0]))
dispparams.CNamedArgs = 1
}
var vargs []VARIANT
if len(params) > 0 {
vargs = make([]VARIANT, len(params))
for i, v := range params {
//n := len(params)-i-1
n := len(params) - i - 1
VariantInit(&vargs[n])
switch v.(type) {
case bool:
if v.(bool) {
vargs[n] = VARIANT{VT_BOOL, 0, 0, 0, 0xffff}
} else {
vargs[n] = VARIANT{VT_BOOL, 0, 0, 0, 0}
}
case *bool:
vargs[n] = VARIANT{VT_BOOL | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*bool))))}
case byte:
vargs[n] = VARIANT{VT_I1, 0, 0, 0, int64(v.(byte))}
case *byte:
vargs[n] = VARIANT{VT_I1 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*byte))))}
case int16:
vargs[n] = VARIANT{VT_I2, 0, 0, 0, int64(v.(int16))}
case *int16:
vargs[n] = VARIANT{VT_I2 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int16))))}
case uint16:
vargs[n] = VARIANT{VT_UI2, 0, 0, 0, int64(v.(int16))}
case *uint16:
vargs[n] = VARIANT{VT_UI2 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint16))))}
case int, int32:
vargs[n] = VARIANT{VT_UI4, 0, 0, 0, int64(v.(int))}
case *int, *int32:
vargs[n] = VARIANT{VT_I4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int))))}
case uint, uint32:
vargs[n] = VARIANT{VT_UI4, 0, 0, 0, int64(v.(uint))}
case *uint, *uint32:
vargs[n] = VARIANT{VT_UI4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint))))}
case int64:
vargs[n] = VARIANT{VT_I8, 0, 0, 0, v.(int64)}
case *int64:
vargs[n] = VARIANT{VT_I8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*int64))))}
case uint64:
vargs[n] = VARIANT{VT_UI8, 0, 0, 0, int64(v.(uint64))}
case *uint64:
vargs[n] = VARIANT{VT_UI8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*uint64))))}
case float32:
vargs[n] = VARIANT{VT_R4, 0, 0, 0, int64(v.(float32))}
case *float32:
vargs[n] = VARIANT{VT_R4 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*float32))))}
case float64:
vargs[n] = VARIANT{VT_R8, 0, 0, 0, int64(v.(float64))}
case *float64:
vargs[n] = VARIANT{VT_R8 | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*float64))))}
case string:
vargs[n] = VARIANT{VT_BSTR, 0, 0, 0, int64(uintptr(unsafe.Pointer(SysAllocString(v.(string)))))}
case *string:
vargs[n] = VARIANT{VT_BSTR | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*string))))}
case *IDispatch:
vargs[n] = VARIANT{VT_DISPATCH, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*IDispatch))))}
case **IDispatch:
vargs[n] = VARIANT{VT_DISPATCH | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(**IDispatch))))}
case nil:
vargs[n] = VARIANT{VT_NULL, 0, 0, 0, 0}
case *VARIANT:
vargs[n] = VARIANT{VT_VARIANT | VT_BYREF, 0, 0, 0, int64(uintptr(unsafe.Pointer(v.(*VARIANT))))}
default:
panic("unknown type")
}
}
dispparams.Rgvarg = uintptr(unsafe.Pointer(&vargs[0]))
dispparams.CArgs = uint32(len(params))
}
var ret VARIANT
var excepInfo EXCEPINFO
VariantInit(&ret)
hr, _, _ := syscall.Syscall9(disp.lpVtbl.pInvoke, 8,
uintptr(unsafe.Pointer(disp)),
uintptr(dispid),
uintptr(unsafe.Pointer(IID_NULL)),
uintptr(GetUserDefaultLCID()),
uintptr(dispatch),
uintptr(unsafe.Pointer(&dispparams)),
uintptr(unsafe.Pointer(&ret)),
uintptr(unsafe.Pointer(&excepInfo)),
0)
if hr != 0 {
if excepInfo.BstrDescription != nil {
bs := UTF16PtrToString(excepInfo.BstrDescription)
panic(bs)
}
}
for _, varg := range vargs {
if varg.VT == VT_BSTR && varg.Val != 0 {
SysFreeString(((*int16)(unsafe.Pointer(uintptr(varg.Val)))))
}
}
result = &ret
return
}

Some files were not shown because too many files have changed in this diff Show More