[v3, windows] Add MainThread dispatching and fixes the blocking window

This commit is contained in:
stffabi 2023-04-26 16:28:27 +02:00 committed by Misite Bao
commit 4a60dfc373
5 changed files with 179 additions and 22 deletions

View file

@ -4,6 +4,7 @@ import (
_ "embed"
"log"
"net/http"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
)
@ -38,6 +39,21 @@ func main() {
println("clicked")
})
go func() {
time.Sleep(5 * time.Second)
app.NewWebviewWindowWithOptions(&application.WebviewWindowOptions{
Title: "Plain Bundle new Window from GoRoutine",
Width: 500,
Height: 500,
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,
TitleBar: application.MacTitleBarHiddenInsetUnified,
InvisibleTitleBarHeight: 50,
},
})
}()
err := app.Run()
if err != nil {

View file

@ -225,6 +225,7 @@ var (
destroyCursor = user32.NewProc("DestroyCursor")
getDlgCtrlID = user32.NewProc("GetDlgCtrlID")
systemParametersInfo = user32.NewProc("SystemParametersInfoW")
registerWindowMessage = user32.NewProc("RegisterWindowMessageW")
regCreateKeyEx = advapi32.NewProc("RegCreateKeyExW")
regOpenKeyEx = advapi32.NewProc("RegOpenKeyExW")
@ -364,6 +365,7 @@ var (
mulDiv = kernel32.NewProc("MulDiv")
getConsoleWindow = kernel32.NewProc("GetConsoleWindow")
getCurrentThread = kernel32.NewProc("GetCurrentThread")
getCurrentThreadId = kernel32.NewProc("GetCurrentThreadId")
getLogicalDrives = kernel32.NewProc("GetLogicalDrives")
getDriveType = kernel32.NewProc("GetDriveTypeW")
getUserDefaultLCID = kernel32.NewProc("GetUserDefaultLCID")
@ -482,6 +484,14 @@ var (
setProcessDpiAwareness = shcore.NewProc("SetProcessDpiAwareness")
)
func RegisterWindowMessage(name string) uint32 {
ret, _, _ := registerWindowMessage.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))),
)
return uint32(ret)
}
// RegisterClassEx sets the Size of the WNDCLASSEX automatically.
func RegisterClassEx(wndClassEx *WNDCLASSEX) ATOM {
if wndClassEx != nil {
@ -3674,6 +3684,11 @@ func GetCurrentThread() HANDLE {
return HANDLE(ret)
}
func GetCurrentThreadId() HANDLE {
ret, _, _ := getCurrentThreadId.Call()
return HANDLE(ret)
}
func GetLogicalDrives() uint32 {
ret, _, _ := getLogicalDrives.Call()
return uint32(ret)

View file

@ -3,10 +3,11 @@
package application
import (
"github.com/samber/lo"
"github.com/wailsapp/wails/v3/internal/w32"
"syscall"
"unsafe"
"github.com/samber/lo"
"github.com/wailsapp/wails/v3/internal/w32"
)
var windowClassName = lo.Must(syscall.UTF16PtrFromString("WailsWebviewWindow"))
@ -15,11 +16,9 @@ type windowsApp struct {
parent *App
instance w32.HINSTANCE
}
func (m *windowsApp) dispatchOnMainThread(id uint) {
//TODO implement me
panic("implement me")
mainThreadID w32.HANDLE
mainThreadWindowHWND w32.HWND
}
func (m *windowsApp) getPrimaryScreen() (*Screen, error) {
@ -116,27 +115,20 @@ func (m *windowsApp) init() {
func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr {
switch msg {
case w32.WM_SIZE, w32.WM_PAINT:
case w32.WM_SIZE:
return 0
case w32.WM_CLOSE:
w32.PostQuitMessage(0)
return 0
case wmInvokeCallback:
if hwnd == m.mainThreadWindowHWND {
m.invokeCallback(wParam, lParam)
return 0
}
}
return w32.DefWindowProc(hwnd, msg, wParam, lParam)
}
func (m *windowsApp) runMainLoop() int {
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 newPlatformApp(app *App) *windowsApp {
result := &windowsApp{
parent: app,
@ -144,6 +136,7 @@ func newPlatformApp(app *App) *windowsApp {
}
result.init()
result.initMainLoop()
return result
}

View file

@ -0,0 +1,129 @@
//go:build windows
package application
import (
"runtime"
"sort"
"syscall"
"unsafe"
"github.com/samber/lo"
"github.com/wailsapp/wails/v3/internal/w32"
)
var (
wmInvokeCallback uint32
)
func init() {
wmInvokeCallback = w32.RegisterWindowMessage("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,
lo.Must(syscall.UTF16PtrFromString("__wails_hidden_mainthread")),
w32.WS_DISABLED,
w32.CW_USEDEFAULT,
w32.CW_USEDEFAULT,
0,
0,
0,
0,
w32.GetModuleHandle(""),
nil)
m.mainThreadID, _ = w32.GetWindowThreadProcessId(m.mainThreadWindowHWND)
}
func (m *windowsApp) runMainLoop() int {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if m.invokeRequired() {
panic("invokeRequired for runMainLoop, the mainloop must be running on the same OSThread as the mainThreadWindow has been created on")
}
msg := (*w32.MSG)(unsafe.Pointer(w32.GlobalAlloc(0, uint32(unsafe.Sizeof(w32.MSG{})))))
defer w32.GlobalFree(w32.HGLOBAL(unsafe.Pointer(m)))
for w32.GetMessage(msg, 0, 0, 0) != 0 {
w32.TranslateMessage(msg)
w32.DispatchMessage(msg)
}
return int(msg.WParam)
}
func (m *windowsApp) dispatchOnMainThread(id uint) {
mainThreadHWND := m.mainThreadWindowHWND
if mainThreadHWND == 0 {
panic("initMainLoop was not called")
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if m.invokeRequired() {
w32.PostMessage(mainThreadHWND, wmInvokeCallback, uintptr(id), 0)
} else {
mainThreadFunctionStoreLock.Lock()
fn := mainThreadFunctionStore[id]
delete(mainThreadFunctionStore, id)
mainThreadFunctionStoreLock.Unlock()
if fn == nil {
Fatal("dispatchOnMainThread called with invalid id: %v", id)
}
fn()
}
}
func (m *windowsApp) invokeRequired() bool {
mainThreadID := m.mainThreadID
if mainThreadID == 0 {
panic("initMainLoop was not called")
}
return mainThreadID != w32.GetCurrentThreadId()
}
func (m *windowsApp) invokeCallback(wParam, lParam uintptr) {
// TODO: Should we invoke just one or all queued? In v2 we always invoked all pendings...
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if m.invokeRequired() {
panic("invokeCallback must always be called on the MainOSThread")
}
mainThreadFunctionStoreLock.Lock()
fnIDs := make([]uint, 0, len(mainThreadFunctionStore))
for id := range mainThreadFunctionStore {
fnIDs = append(fnIDs, id)
}
sort.Slice(fnIDs, func(i, j int) bool { return fnIDs[i] < fnIDs[j] })
fns := make([]func(), len(fnIDs))
for i, id := range fnIDs {
fns[i] = mainThreadFunctionStore[id]
delete(mainThreadFunctionStore, id)
}
mainThreadFunctionStoreLock.Unlock()
for _, fn := range fns {
fn()
}
}

View file

@ -3,10 +3,11 @@
package application
import (
"github.com/samber/lo"
"github.com/wailsapp/wails/v3/internal/w32"
"syscall"
"unsafe"
"github.com/samber/lo"
"github.com/wailsapp/wails/v3/internal/w32"
)
var showDevTools = func(window unsafe.Pointer) {}
@ -67,7 +68,10 @@ func (w *windowsWebviewWindow) setBackgroundColour(color *RGBA) {
}
func (w *windowsWebviewWindow) run() {
globalApplication.dispatchOnMainThread(w._run)
}
func (w *windowsWebviewWindow) _run() {
var exStyle uint
options := w.parent.options
windowsOptions := options.Windows
@ -84,7 +88,7 @@ func (w *windowsWebviewWindow) run() {
hwnd = w32.CreateWindowEx(
exStyle,
windowClassName,
lo.Must(syscall.UTF16PtrFromString("My Window Title")),
lo.Must(syscall.UTF16PtrFromString(options.Title)),
w32.WS_OVERLAPPEDWINDOW|w32.WS_VISIBLE,
w32.CW_USEDEFAULT,
w32.CW_USEDEFAULT,