2014-02-11 15:14:15 -06:00
// 11 february 2014
2014-03-12 20:55:45 -05:00
2014-02-19 10:41:10 -06:00
package ui
2014-02-11 15:14:15 -06:00
import (
2014-04-01 18:10:30 -05:00
"fmt"
2014-02-11 15:14:15 -06:00
"syscall"
"unsafe"
)
2014-02-19 21:59:48 -06:00
/ *
problem : messages have to be dispatched on the same thread as system calls , and we can ' t mux GetMessage ( ) with select , and PeekMessage ( ) every iteration is wasteful ( and leads to lag for me ( only ) with the concurrent garbage collector sweep )
2014-04-01 18:10:30 -05:00
possible : solution : use PostThreadMessage ( ) to send uimsgs out to the message loop , which runs on its own goroutine
( I had come up with this first but wanted to try other things before doing it ( and wasn ' t really sure if user - defined messages were safe , not quite understanding the system ) ; nsf came up with it independently and explained that this was really the only right way to do it , so thanks to him )
2014-04-27 20:33:48 -05:00
problem : if the thread isn ' t in its main message pump , the thread message is simply lost ( see , for example , http : //blogs.msdn.com/b/oldnewthing/archive/2005/04/26/412116.aspx)
2014-04-30 13:35:33 -05:00
this happened when scrolling Areas ( as scrolling is modal ; see http : //blogs.msdn.com/b/oldnewthing/archive/2005/04/27/412565.aspx)
2014-04-01 18:10:30 -05:00
the only recourse , and the one both Microsoft ( http : //support.microsoft.com/kb/183116) and Raymond Chen (http://blogs.msdn.com/b/oldnewthing/archive/2008/12/23/9248851.aspx) suggest (and Treeki/Ninjifox confirmed), is to create an invisible window to dispatch messages instead.
yay .
2014-02-19 21:59:48 -06:00
* /
2014-06-30 21:42:48 -05:00
var uitask chan interface { }
2014-02-11 15:14:15 -06:00
2014-06-30 21:42:48 -05:00
type uimsg struct {
call * syscall . LazyProc
p [ ] uintptr
ret chan uiret
}
type uiret struct {
ret uintptr
err error
2014-02-11 15:14:15 -06:00
}
2014-02-19 21:59:48 -06:00
const (
2014-06-10 08:55:14 -05:00
msgRequested = _WM_APP + iota + 1 // + 1 just to be safe
2014-03-05 12:21:45 -06:00
msgQuit
2014-03-29 17:51:22 -05:00
msgSetAreaSize
2014-06-09 21:26:37 -05:00
msgRepaintAll
2014-02-19 21:59:48 -06:00
)
var (
2014-04-01 18:10:30 -05:00
_postMessage = user32 . NewProc ( "PostMessageW" )
2014-02-19 21:59:48 -06:00
)
2014-06-30 21:48:12 -05:00
var msghwnd _HWND
2014-06-28 15:37:55 -05:00
2014-06-30 21:48:12 -05:00
func uiinit ( ) error {
2014-03-01 14:18:29 -06:00
err := doWindowsInit ( )
if err != nil {
2014-04-01 18:10:30 -05:00
return fmt . Errorf ( "error doing general Windows initialization: %v" , err )
2014-03-01 14:18:29 -06:00
}
2014-02-11 15:14:15 -06:00
2014-06-30 21:48:12 -05:00
msghwnd , err = makeMessageHandler ( )
2014-04-01 18:10:30 -05:00
if err != nil {
return fmt . Errorf ( "error making invisible window for handling events: %v" , err )
}
2014-04-01 14:14:57 -05:00
2014-06-30 21:48:12 -05:00
// do this only on success just to be safe
uitask = make ( chan interface { } )
return nil
}
func ui ( ) {
2014-06-30 21:42:48 -05:00
go func ( ) {
2014-06-30 21:48:12 -05:00
for {
select {
case m := <- uitask :
r1 , _ , err := _postMessage . Call (
uintptr ( msghwnd ) ,
msgRequested ,
uintptr ( 0 ) ,
2014-06-30 21:42:48 -05:00
uintptr ( unsafe . Pointer ( & m ) ) )
2014-06-30 21:48:12 -05:00
if r1 == 0 { // failure
panic ( "error sending message to message loop to call function: " + err . Error ( ) )
}
case <- Stop :
r1 , _ , err := _postMessage . Call (
uintptr ( msghwnd ) ,
msgQuit ,
uintptr ( 0 ) ,
uintptr ( 0 ) )
if r1 == 0 { // failure
panic ( "error sending quit message to message loop: " + err . Error ( ) )
}
2014-06-30 21:42:48 -05:00
}
}
} ( )
2014-04-01 14:14:57 -05:00
msgloop ( )
2014-02-11 15:14:15 -06:00
}
2014-02-11 17:31:24 -06:00
var (
2014-06-10 08:55:14 -05:00
_dispatchMessage = user32 . NewProc ( "DispatchMessageW" )
2014-06-26 19:20:00 -05:00
_getActiveWindow = user32 . NewProc ( "GetActiveWindow" )
2014-06-10 08:55:14 -05:00
_getMessage = user32 . NewProc ( "GetMessageW" )
2014-06-26 19:20:00 -05:00
_isDialogMessage = user32 . NewProc ( "IsDialogMessageW" )
2014-06-10 08:55:14 -05:00
_postQuitMessage = user32 . NewProc ( "PostQuitMessage" )
_sendMessage = user32 . NewProc ( "SendMessageW" )
2014-02-11 17:31:24 -06:00
_translateMessage = user32 . NewProc ( "TranslateMessage" )
)
2014-04-01 14:14:57 -05:00
func msgloop ( ) {
2014-02-11 17:31:24 -06:00
var msg struct {
2014-06-10 08:55:14 -05:00
hwnd _HWND
message uint32
wParam _WPARAM
lParam _LPARAM
time uint32
pt _POINT
2014-02-11 17:31:24 -06:00
}
2014-02-19 21:59:48 -06:00
for {
r1 , _ , err := _getMessage . Call (
uintptr ( unsafe . Pointer ( & msg ) ) ,
uintptr ( _NULL ) ,
uintptr ( 0 ) ,
uintptr ( 0 ) )
2014-06-10 08:55:14 -05:00
if r1 == negConst ( - 1 ) { // error
2014-04-01 14:14:57 -05:00
panic ( "error getting message in message loop: " + err . Error ( ) )
2014-02-19 21:59:48 -06:00
}
2014-06-10 08:55:14 -05:00
if r1 == 0 { // WM_QUIT message
2014-02-19 21:59:48 -06:00
return
}
2014-06-26 19:20:00 -05:00
// this next bit handles tab stops
r1 , _ , _ = _getActiveWindow . Call ( )
r1 , _ , _ = _isDialogMessage . Call (
r1 , // active window
uintptr ( unsafe . Pointer ( & msg ) ) )
if r1 != 0 {
continue
}
2014-02-19 21:59:48 -06:00
_translateMessage . Call ( uintptr ( unsafe . Pointer ( & msg ) ) )
_dispatchMessage . Call ( uintptr ( unsafe . Pointer ( & msg ) ) )
2014-02-11 17:31:24 -06:00
}
}
2014-04-01 18:10:30 -05:00
2014-06-03 09:48:21 -05:00
var (
msghandlerclass = toUTF16 ( "gomsghandler" )
msghandlertitle = toUTF16 ( "ui package message window" )
2014-04-01 18:10:30 -05:00
)
func makeMessageHandler ( ) ( hwnd _HWND , err error ) {
wc := & _WNDCLASS {
2014-06-10 08:55:14 -05:00
lpszClassName : utf16ToArg ( msghandlerclass ) ,
lpfnWndProc : syscall . NewCallback ( messageHandlerWndProc ) ,
hInstance : hInstance ,
hIcon : icon ,
hCursor : cursor ,
hbrBackground : _HBRUSH ( _COLOR_BTNFACE + 1 ) ,
2014-04-01 18:10:30 -05:00
}
r1 , _ , err := _registerClass . Call ( uintptr ( unsafe . Pointer ( wc ) ) )
2014-06-10 08:55:14 -05:00
if r1 == 0 { // failure
2014-04-01 18:10:30 -05:00
return _HWND ( _NULL ) , fmt . Errorf ( "error registering the class of the invisible window for handling events: %v" , err )
}
r1 , _ , err = _createWindowEx . Call (
uintptr ( 0 ) ,
2014-06-03 09:48:21 -05:00
utf16ToArg ( msghandlerclass ) ,
utf16ToArg ( msghandlertitle ) ,
2014-04-01 18:10:30 -05:00
uintptr ( 0 ) ,
2014-05-25 11:31:38 -05:00
negConst ( _CW_USEDEFAULT ) ,
negConst ( _CW_USEDEFAULT ) ,
negConst ( _CW_USEDEFAULT ) ,
negConst ( _CW_USEDEFAULT ) ,
2014-05-25 14:23:11 -05:00
// don't negConst() HWND_MESSAGE; windowsconstgen was given a pointer by windows.h, and pointers are unsigned, so converting it back to signed doesn't work
uintptr ( _HWND_MESSAGE ) ,
2014-04-01 18:10:30 -05:00
uintptr ( _NULL ) ,
uintptr ( hInstance ) ,
uintptr ( _NULL ) )
2014-06-10 08:55:14 -05:00
if r1 == 0 { // failure
2014-04-01 18:10:30 -05:00
return _HWND ( _NULL ) , fmt . Errorf ( "error actually creating invisible window for handling events: %v" , err )
}
return _HWND ( r1 ) , nil
}
func messageHandlerWndProc ( hwnd _HWND , uMsg uint32 , wParam _WPARAM , lParam _LPARAM ) _LRESULT {
switch uMsg {
case msgRequested :
2014-06-30 21:42:48 -05:00
mt := ( * interface { } ) ( unsafe . Pointer ( lParam ) )
switch m := ( * mt ) . ( type ) {
case * uimsg :
r1 , _ , err := m . call . Call ( m . p ... )
m . ret <- uiret {
ret : r1 ,
err : err ,
}
case func ( ) :
m ( )
}
2014-04-01 18:10:30 -05:00
return 0
case msgQuit :
// does not return a value according to MSDN
_postQuitMessage . Call ( 0 )
return 0
}
2014-05-30 13:13:47 -05:00
return defWindowProc ( hwnd , uMsg , wParam , lParam )
2014-04-01 18:10:30 -05:00
}