2023-04-26 16:28:27 +02:00
//go:build windows
package application
import (
"runtime"
"sort"
"unsafe"
2023-07-15 23:56:26 +02:00
"github.com/wailsapp/wails/v3/pkg/w32"
2023-04-26 16:28:27 +02:00
)
var (
wmInvokeCallback uint32
)
func init () {
2023-04-28 21:11:49 +10:00
wmInvokeCallback = w32 . RegisterWindowMessage ( w32 . MustStringToUTF16Ptr ( "WailsV0.InvokeCallback" ))
2023-04-26 16:28:27 +02:00
}
// 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 ,
2023-05-01 10:52:46 +10:00
w32 . MustStringToUTF16Ptr ( "__wails_hidden_mainthread" ),
2023-04-26 16:28:27 +02:00
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 {})))))
2023-07-15 23:56:26 +02:00
defer w32 . GlobalFree ( w32 . HGLOBAL ( unsafe . Pointer ( msg )))
2023-04-26 16:28:27 +02:00
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 ()
}
}