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:
commit
f5557c612a
@ -22,6 +22,7 @@ import (
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/flags"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/gomod"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
|
||||
"golang.org/x/mod/semver"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/buildtags"
|
||||
|
||||
@ -36,6 +37,10 @@ import (
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
)
|
||||
|
||||
const (
|
||||
viteMinVersion = "v3.0.0"
|
||||
)
|
||||
|
||||
func sliceToMap(input []string) map[string]struct{} {
|
||||
result := map[string]struct{}{}
|
||||
for _, value := range input {
|
||||
@ -88,10 +93,11 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
||||
buildOptions.IgnoreApplication = false
|
||||
}
|
||||
|
||||
legacyUseDevServerInsteadofCustomScheme := false
|
||||
// frontend:dev:watcher command.
|
||||
frontendDevAutoDiscovery := projectConfig.IsFrontendDevServerURLAutoDiscovery()
|
||||
if command := projectConfig.DevWatcherCommand; command != "" {
|
||||
closer, devServerURL, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
|
||||
closer, devServerURL, devServerViteVersion, err := runFrontendDevWatcherCommand(projectConfig.GetFrontendDir(), command, frontendDevAutoDiscovery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -100,6 +106,12 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
||||
f.FrontendDevServerURL = devServerURL
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if devServerViteVersion != "" && semver.Compare(devServerViteVersion, viteMinVersion) < 0 {
|
||||
logutils.LogRed("Please upgrade your Vite Server to at least '%s' future Wails versions will require at least Vite '%s'", viteMinVersion, viteMinVersion)
|
||||
time.Sleep(3 * time.Second)
|
||||
legacyUseDevServerInsteadofCustomScheme = true
|
||||
}
|
||||
} else if frontendDevAutoDiscovery {
|
||||
return fmt.Errorf("unable to auto discover frontend:dev:serverUrl without a frontend:dev:watcher command, please either set frontend:dev:watcher or remove the auto discovery from frontend:dev:serverUrl")
|
||||
}
|
||||
@ -107,7 +119,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
||||
// Do initial build but only for the application.
|
||||
logger.Println("Building application for development...")
|
||||
buildOptions.IgnoreFrontend = true
|
||||
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel)
|
||||
debugBinaryProcess, appBinary, err := restartApp(buildOptions, nil, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
|
||||
buildOptions.IgnoreFrontend = ignoreFrontend || f.FrontendDevServerURL != ""
|
||||
if err != nil {
|
||||
return err
|
||||
@ -153,7 +165,7 @@ func Application(f *flags.Dev, logger *clilogger.CLILogger) error {
|
||||
}()
|
||||
|
||||
// Watch for changes and trigger restartApp()
|
||||
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL())
|
||||
debugBinaryProcess = doWatcherLoop(buildOptions, debugBinaryProcess, f, watcher, exitCodeChannel, quitChannel, f.DevServerURL(), legacyUseDevServerInsteadofCustomScheme)
|
||||
|
||||
// Kill the current program if running and remove dev binary
|
||||
if err := killProcessAndCleanupBinary(debugBinaryProcess, appBinary); err != nil {
|
||||
@ -202,7 +214,7 @@ func runCommand(dir string, exitOnError bool, command string, args ...string) er
|
||||
}
|
||||
|
||||
// runFrontendDevWatcherCommand will run the `frontend:dev:watcher` command if it was given, ex- `npm run dev`
|
||||
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, error) {
|
||||
func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, discoverViteServerURL bool) (func(), string, string, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
scanner := NewStdoutScanner()
|
||||
cmdSlice := strings.Split(devCommand, " ")
|
||||
@ -214,7 +226,7 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
cancel()
|
||||
return nil, "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
|
||||
return nil, "", "", fmt.Errorf("unable to start frontend DevWatcher: %w", err)
|
||||
}
|
||||
|
||||
var viteServerURL string
|
||||
@ -224,10 +236,19 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
||||
viteServerURL = serverURL
|
||||
case <-time.After(time.Second * 10):
|
||||
cancel()
|
||||
return nil, "", errors.New("failed to find Vite server URL")
|
||||
return nil, "", "", errors.New("failed to find Vite server URL")
|
||||
}
|
||||
}
|
||||
|
||||
viteVersion := ""
|
||||
select {
|
||||
case version := <-scanner.ViteServerVersionC:
|
||||
viteVersion = version
|
||||
|
||||
case <-time.After(time.Second * 5):
|
||||
// That's fine, then most probably it was not vite that was running
|
||||
}
|
||||
|
||||
logutils.LogGreen("Running frontend DevWatcher command: '%s'", devCommand)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
@ -255,11 +276,11 @@ func runFrontendDevWatcherCommand(frontendDirectory string, devCommand string, d
|
||||
}
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}, viteServerURL, nil
|
||||
}, viteServerURL, viteVersion, nil
|
||||
}
|
||||
|
||||
// restartApp does the actual rebuilding of the application when files change
|
||||
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int) (*process.Process, string, error) {
|
||||
func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, exitCodeChannel chan int, legacyUseDevServerInsteadofCustomScheme bool) (*process.Process, string, error) {
|
||||
|
||||
appBinary, err := build.Build(buildOptions)
|
||||
println()
|
||||
@ -297,6 +318,9 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
|
||||
os.Setenv("assetdir", f.AssetDir)
|
||||
os.Setenv("devserver", f.DevServer)
|
||||
os.Setenv("frontenddevserverurl", f.FrontendDevServerURL)
|
||||
if legacyUseDevServerInsteadofCustomScheme {
|
||||
os.Setenv("legacyusedevsererinsteadofcustomscheme", "true")
|
||||
}
|
||||
|
||||
// Start up new binary with correct args
|
||||
newProcess := process.NewProcess(appBinary, args...)
|
||||
@ -316,7 +340,7 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process
|
||||
}
|
||||
|
||||
// doWatcherLoop is the main watch loop that runs while dev is active
|
||||
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) *process.Process {
|
||||
func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, f *flags.Dev, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL, legacyUseDevServerInsteadofCustomScheme bool) *process.Process {
|
||||
// Main Loop
|
||||
var extensionsThatTriggerARebuild = sliceToMap(strings.Split(f.Extensions, ","))
|
||||
var dirsThatTriggerAReload []string
|
||||
@ -422,7 +446,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc
|
||||
rebuild = false
|
||||
logutils.LogGreen("[Rebuild triggered] files updated")
|
||||
// Try and build the app
|
||||
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel)
|
||||
newBinaryProcess, _, err := restartApp(buildOptions, debugBinaryProcess, f, exitCodeChannel, legacyUseDevServerInsteadofCustomScheme)
|
||||
if err != nil {
|
||||
logutils.LogRed("Error during build: %s", err.Error())
|
||||
continue
|
||||
|
@ -2,30 +2,47 @@ package dev
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/logutils"
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// stdoutScanner acts as a stdout target that will scan the incoming
|
||||
// data to find out the vite server url
|
||||
type stdoutScanner struct {
|
||||
ViteServerURLChan chan string
|
||||
ViteServerURLChan chan string
|
||||
ViteServerVersionC chan string
|
||||
versionDetected bool
|
||||
}
|
||||
|
||||
// NewStdoutScanner creates a new stdoutScanner
|
||||
func NewStdoutScanner() *stdoutScanner {
|
||||
return &stdoutScanner{
|
||||
ViteServerURLChan: make(chan string, 2),
|
||||
ViteServerURLChan: make(chan string, 2),
|
||||
ViteServerVersionC: make(chan string, 2),
|
||||
}
|
||||
}
|
||||
|
||||
// Write bytes to the scanner. Will copy the bytes to stdout
|
||||
func (s *stdoutScanner) Write(data []byte) (n int, err error) {
|
||||
input := stripansi.Strip(string(data))
|
||||
if !s.versionDetected {
|
||||
v, err := detectViteVersion(input)
|
||||
if v != "" || err != nil {
|
||||
if err != nil {
|
||||
logutils.LogRed("ViteStdoutScanner: %s", err)
|
||||
v = "v0.0.0"
|
||||
}
|
||||
s.ViteServerVersionC <- v
|
||||
s.versionDetected = true
|
||||
}
|
||||
}
|
||||
|
||||
match := strings.Index(input, "Local:")
|
||||
if match != -1 {
|
||||
sc := bufio.NewScanner(strings.NewReader(input))
|
||||
@ -47,3 +64,21 @@ func (s *stdoutScanner) Write(data []byte) (n int, err error) {
|
||||
}
|
||||
return os.Stdout.Write(data)
|
||||
}
|
||||
|
||||
func detectViteVersion(line string) (string, error) {
|
||||
s := strings.Split(strings.TrimSpace(line), " ")
|
||||
if strings.ToLower(s[0]) != "vite" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(line) < 2 {
|
||||
return "", fmt.Errorf("unable to parse vite version")
|
||||
}
|
||||
|
||||
v := s[1]
|
||||
if !semver.IsValid(v) {
|
||||
return "", fmt.Errorf("%s is not a valid vite version string", v)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
@ -8,6 +8,6 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^2.9.9"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
@ -8,9 +8,11 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
iofs "io/fs"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver"
|
||||
|
||||
@ -104,17 +106,35 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
||||
}
|
||||
|
||||
if frontendDevServerURL != "" {
|
||||
if devServer == "" {
|
||||
return nil, fmt.Errorf("Unable to use FrontendDevServerUrl without a DevServer address")
|
||||
if os.Getenv("legacyusedevsererinsteadofcustomscheme") != "" {
|
||||
startURL, err := url.Parse("http://" + devServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, "starturl", startURL)
|
||||
}
|
||||
|
||||
startURL, err := url.Parse("http://" + devServer)
|
||||
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
|
||||
|
||||
externalURL, err := url.Parse(frontendDevServerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, "starturl", startURL)
|
||||
ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL)
|
||||
if externalURL.Host == "" {
|
||||
return nil, fmt.Errorf("Invalid frontend:dev:serverUrl missing protocol scheme?")
|
||||
}
|
||||
|
||||
waitCb := func() { myLogger.Debug("Waiting for frontend DevServer '%s' to be ready", externalURL) }
|
||||
if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) {
|
||||
myLogger.Error("Timeout waiting for frontend DevServer")
|
||||
}
|
||||
|
||||
handler := assetserver.NewExternalAssetsHandler(myLogger, assetConfig, externalURL)
|
||||
assetConfig.Assets = nil
|
||||
assetConfig.Handler = handler
|
||||
assetConfig.Middleware = nil
|
||||
|
||||
myLogger.Info("Serving assets from frontend DevServer URL: %s", frontendDevServerURL)
|
||||
} else {
|
||||
@ -246,3 +266,22 @@ func tryInferAssetDirFromFS(assets iofs.FS) (string, error) {
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func checkPortIsOpen(host string, timeout time.Duration, waitCB func()) (ret bool) {
|
||||
if timeout == 0 {
|
||||
timeout = time.Minute
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
conn, _ := net.DialTimeout("tcp", host, 2*time.Second)
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
waitCB()
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -12,5 +12,6 @@ typedef int Role;
|
||||
|
||||
static const Role AppMenu = 1;
|
||||
static const Role EditMenu = 2;
|
||||
static const Role WindowMenu = 3;
|
||||
|
||||
#endif /* Role_h */
|
||||
|
@ -68,12 +68,20 @@
|
||||
appName = [[NSProcessInfo processInfo] processName];
|
||||
}
|
||||
WailsMenu *appMenu = [[[WailsMenu new] initWithNSTitle:appName] autorelease];
|
||||
|
||||
if (ctx.aboutTitle != nil) {
|
||||
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
[appMenu addItem:[self newMenuItem:[@"Hide " stringByAppendingString:appName] :@selector(hide:) :@"h" :NSEventModifierFlagCommand]];
|
||||
[appMenu addItem:[self newMenuItem:@"Hide Others" :@selector(hideOtherApplications:) :@"h" :(NSEventModifierFlagOption | NSEventModifierFlagCommand)]];
|
||||
[appMenu addItem:[self newMenuItem:@"Show All" :@selector(unhideAllApplications:) :@""]];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
id quitTitle = [@"Quit " stringByAppendingString:appName];
|
||||
NSMenuItem* quitMenuItem = [self newMenuItem:quitTitle :@selector(Quit) :@"q" :NSEventModifierFlagCommand];
|
||||
quitMenuItem.target = ctx;
|
||||
if (ctx.aboutTitle != nil) {
|
||||
[appMenu addItem:[self newMenuItemWithContext :ctx :[@"About " stringByAppendingString:appName] :@selector(About) :nil :0]];
|
||||
}
|
||||
[appMenu addItem:quitMenuItem];
|
||||
[self appendSubmenu:appMenu];
|
||||
break;
|
||||
@ -100,6 +108,17 @@
|
||||
[editMenu appendSubmenu:speechMenu];
|
||||
[self appendSubmenu:editMenu];
|
||||
|
||||
break;
|
||||
}
|
||||
case WindowMenu:
|
||||
{
|
||||
WailsMenu *windowMenu = [[[WailsMenu new] initWithNSTitle:@"Window"] autorelease];
|
||||
[windowMenu addItem:[self newMenuItem:@"Minimize" :@selector(performMiniaturize:) :@"m" :NSEventModifierFlagCommand]];
|
||||
[windowMenu addItem:[self newMenuItem:@"Zoom" :@selector(performZoom:) :@""]];
|
||||
[windowMenu addItem:[NSMenuItem separatorItem]];
|
||||
[windowMenu addItem:[self newMenuItem:@"Full Screen" :@selector(enterFullScreenMode:) :@"f" :(NSEventModifierFlagControl | NSEventModifierFlagCommand)]];
|
||||
[self appendSubmenu:windowMenu];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
//go:build dev
|
||||
// +build dev
|
||||
|
||||
package devserver
|
||||
package assetserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -10,21 +10,12 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
)
|
||||
|
||||
func newExternalDevServerAssetHandler(logger *logger.Logger, url *url.URL, options assetserver.Options) http.Handler {
|
||||
handler := newExternalAssetsHandler(logger, url, options.Handler)
|
||||
func NewExternalAssetsHandler(logger Logger, options assetserver.Options, url *url.URL) http.Handler {
|
||||
baseHandler := options.Handler
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.Handler) http.Handler {
|
||||
errSkipProxy := fmt.Errorf("skip proxying")
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
||||
@ -37,7 +28,7 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
||||
}
|
||||
|
||||
proxy.ModifyResponse = func(res *http.Response) error {
|
||||
if handler == nil {
|
||||
if baseHandler == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -53,11 +44,11 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
||||
}
|
||||
|
||||
proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) {
|
||||
if handler != nil && errors.Is(err, errSkipProxy) {
|
||||
if baseHandler != nil && errors.Is(err, errSkipProxy) {
|
||||
if logger != nil {
|
||||
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using AssetHandler", r.URL)
|
||||
logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL)
|
||||
}
|
||||
handler.ServeHTTP(rw, r)
|
||||
baseHandler.ServeHTTP(rw, r)
|
||||
} else {
|
||||
if logger != nil {
|
||||
logger.Error("[ExternalAssetHandler] Proxy error: %v", err)
|
||||
@ -66,18 +57,24 @@ func newExternalAssetsHandler(logger *logger.Logger, url *url.URL, handler http.
|
||||
}
|
||||
}
|
||||
|
||||
return http.HandlerFunc(
|
||||
var result http.Handler = http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method == http.MethodGet {
|
||||
proxy.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
if handler != nil {
|
||||
handler.ServeHTTP(rw, req)
|
||||
if baseHandler != nil {
|
||||
baseHandler.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusMethodNotAllowed)
|
||||
})
|
||||
|
||||
if middleware := options.Middleware; middleware != nil {
|
||||
result = middleware(result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
@ -56,13 +56,7 @@ func (r legacyRequest) Response() webview.ResponseWriter {
|
||||
return &legacyRequestNoOpCloserResponseWriter{r.rw}
|
||||
}
|
||||
|
||||
func (r legacyRequest) AddRef() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r legacyRequest) Release() error {
|
||||
return nil
|
||||
}
|
||||
func (r legacyRequest) Close() error { return nil }
|
||||
|
||||
func (r *legacyRequest) request() (*http.Request, error) {
|
||||
if r.req != nil {
|
||||
@ -81,6 +75,4 @@ type legacyRequestNoOpCloserResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (*legacyRequestNoOpCloserResponseWriter) Finish() error {
|
||||
return nil
|
||||
}
|
||||
func (*legacyRequestNoOpCloserResponseWriter) Finish() {}
|
||||
|
@ -22,6 +22,7 @@ type assetServerWebView struct {
|
||||
|
||||
// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server.
|
||||
// The request will be finished with a StatusNotImplemented code if no handler has written to the response.
|
||||
// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way.
|
||||
func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
d.dispatchInit.Do(func() {
|
||||
workers := d.dispatchWorkers
|
||||
@ -33,8 +34,11 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
for req := range workerC {
|
||||
uri, _ := req.URL()
|
||||
d.processWebViewRequest(req)
|
||||
req.Release()
|
||||
if err := req.Close(); err != nil {
|
||||
d.logError("Unable to call close for request for uri '%s'", uri)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -45,12 +49,6 @@ func (d *AssetServer) ServeWebViewRequest(req webview.Request) {
|
||||
d.dispatchReqC = dispatchC
|
||||
})
|
||||
|
||||
if err := req.AddRef(); err != nil {
|
||||
uri, _ := req.URL()
|
||||
d.logError("Unable to call AddRef for request '%s'", uri)
|
||||
return
|
||||
}
|
||||
|
||||
d.dispatchReqC <- req
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,5 @@ type Request interface {
|
||||
|
||||
Response() ResponseWriter
|
||||
|
||||
AddRef() error
|
||||
Release() error
|
||||
Close() error
|
||||
}
|
||||
|
@ -118,11 +118,9 @@ import (
|
||||
)
|
||||
|
||||
// NewRequest creates as new WebViewRequest based on a pointer to an `id<WKURLSchemeTask>`
|
||||
//
|
||||
// Please make sure to call Release() when finished using the request.
|
||||
func NewRequest(wkURLSchemeTask unsafe.Pointer) Request {
|
||||
C.URLSchemeTaskRetain(wkURLSchemeTask)
|
||||
return &request{task: wkURLSchemeTask}
|
||||
return newRequestFinalizer(&request{task: wkURLSchemeTask})
|
||||
}
|
||||
|
||||
var _ Request = &request{}
|
||||
@ -135,16 +133,6 @@ type request struct {
|
||||
rw *responseWriter
|
||||
}
|
||||
|
||||
func (r *request) AddRef() error {
|
||||
C.URLSchemeTaskRetain(r.task)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) Release() error {
|
||||
C.URLSchemeTaskRelease(r.task)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *request) URL() (string, error) {
|
||||
return C.GoString(C.URLSchemeTaskRequestURL(r.task)), nil
|
||||
}
|
||||
@ -205,6 +193,16 @@ func (r *request) Response() ResponseWriter {
|
||||
return r.rw
|
||||
}
|
||||
|
||||
func (r *request) Close() error {
|
||||
var err error
|
||||
if r.body != nil {
|
||||
err = r.body.Close()
|
||||
}
|
||||
r.Response().Finish()
|
||||
C.URLSchemeTaskRelease(r.task)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = &requestBodyStreamReader{}
|
||||
|
||||
type requestBodyStreamReader struct {
|
||||
|
40
v2/pkg/assetserver/webview/request_finalizer.go
Normal file
40
v2/pkg/assetserver/webview/request_finalizer.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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.)
|
||||
|
@ -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...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -11,6 +11,6 @@
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"svelte": "^3.49.0",
|
||||
"vite": "^3.0.0"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
@ -9,6 +9,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.5.4",
|
||||
"vite": "^2.9.9"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
@ -8,6 +8,6 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^2.9.9"
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
294
v3/STATUS.md
294
v3/STATUS.md
@ -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
|
||||
|
@ -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.
|
@ -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 => ../..
|
||||
|
@ -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=
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -24,7 +24,7 @@ func main() {
|
||||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Screen Demo",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
|
@ -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,
|
||||
|
@ -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).
|
||||
|
@ -24,7 +24,7 @@ func main() {
|
||||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
|
||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Wails ML Demo",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
|
@ -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
|
||||
|
@ -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=
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
126
v3/pkg/application/mainthread_windows.go
Normal file
126
v3/pkg/application/mainthread_windows.go
Normal 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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -177,3 +177,7 @@ func newMinimizeMenuItem() *MenuItem {
|
||||
func newZoomMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newFullScreenMenuItem() *MenuItem {
|
||||
panic("implement me")
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -12,6 +12,7 @@ type Options struct {
|
||||
Description string
|
||||
Icon []byte
|
||||
Mac MacOptions
|
||||
Windows WindowsApplicationOptions
|
||||
Bind []any
|
||||
Logger struct {
|
||||
Silent bool
|
||||
|
@ -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
|
||||
)
|
||||
|
62
v3/pkg/application/options_win.go
Normal file
62
v3/pkg/application/options_win.go
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
143
v3/pkg/w32/clipboard.go
Normal 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
112
v3/pkg/w32/comctl32.go
Normal 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
40
v3/pkg/w32/comdlg32.go
Normal 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
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
83
v3/pkg/w32/consts.go
Normal 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
36
v3/pkg/w32/dwmapi.go
Normal 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
526
v3/pkg/w32/gdi32.go
Normal 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
177
v3/pkg/w32/gdiplus.go
Normal 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
45
v3/pkg/w32/idispatch.go
Normal 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
33
v3/pkg/w32/istream.go
Normal 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
29
v3/pkg/w32/iunknown.go
Normal 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
332
v3/pkg/w32/kernel32.go
Normal 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
65
v3/pkg/w32/ole32.go
Normal 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
50
v3/pkg/w32/oleaut32.go
Normal 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
118
v3/pkg/w32/screen.go
Normal 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
29
v3/pkg/w32/shcore.go
Normal 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
235
v3/pkg/w32/shell32.go
Normal 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
26
v3/pkg/w32/shlwapi.go
Normal 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
119
v3/pkg/w32/theme.go
Normal 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
216
v3/pkg/w32/toolbar.go
Normal 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
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
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
230
v3/pkg/w32/utils.go
Normal 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
Loading…
Reference in New Issue
Block a user