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"
"runtime"
)
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-02-11 15:14:15 -06:00
var uitask chan * uimsg
type uimsg struct {
call * syscall . LazyProc
p [ ] uintptr
ret chan uiret
}
type uiret struct {
ret uintptr
err error
}
2014-02-19 21:59:48 -06:00
const (
_WM_APP = 0x8000 + iota
msgRequested
2014-03-05 12:21:45 -06:00
msgQuit
2014-03-29 17:51:22 -05:00
msgSetAreaSize
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-03-01 14:18:29 -06:00
func ui ( main func ( ) ) error {
2014-02-11 15:14:15 -06:00
runtime . LockOSThread ( )
uitask = make ( chan * uimsg )
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-04-01 18:10:30 -05:00
hwnd , err := makeMessageHandler ( )
if err != nil {
return fmt . Errorf ( "error making invisible window for handling events: %v" , err )
}
2014-04-01 14:14:57 -05:00
go func ( ) {
for m := range uitask {
2014-04-01 18:10:30 -05:00
// TODO use _sendMessage instead?
r1 , _ , err := _postMessage . Call (
uintptr ( hwnd ) ,
2014-04-01 14:14:57 -05:00
msgRequested ,
uintptr ( 0 ) ,
uintptr ( unsafe . Pointer ( m ) ) )
if r1 == 0 { // failure
2014-04-01 14:16:29 -05:00
panic ( "error sending message to message loop to call function: " + err . Error ( ) )
2014-04-01 14:14:57 -05:00
}
}
} ( )
2014-02-19 21:59:48 -06:00
2014-03-05 12:21:45 -06:00
go func ( ) {
main ( )
2014-04-01 18:10:30 -05:00
r1 , _ , err := _postMessage . Call (
uintptr ( hwnd ) ,
2014-03-05 12:21:45 -06:00
msgQuit ,
uintptr ( 0 ) ,
uintptr ( 0 ) )
if r1 == 0 { // failure
2014-04-01 14:14:57 -05:00
panic ( "error sending quit message to message loop: " + err . Error ( ) )
2014-03-05 12:21:45 -06:00
}
} ( )
2014-03-01 14:18:29 -06:00
2014-04-01 14:14:57 -05:00
msgloop ( )
2014-03-01 14:18:29 -06:00
return nil
2014-02-11 15:14:15 -06:00
}
2014-02-11 17:31:24 -06:00
var (
_dispatchMessage = user32 . NewProc ( "DispatchMessageW" )
_getMessage = user32 . NewProc ( "GetMessageW" )
_postQuitMessage = user32 . NewProc ( "PostQuitMessage" )
_sendMessage = user32 . NewProc ( "SendMessageW" )
_translateMessage = user32 . NewProc ( "TranslateMessage" )
)
2014-02-11 18:18:03 -06:00
var getMessageFail = - 1 // because Go doesn't let me
2014-04-01 14:14:57 -05:00
func msgloop ( ) {
2014-02-11 17:31:24 -06:00
var msg struct {
2014-05-11 10:11:00 -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 ) )
if r1 == uintptr ( getMessageFail ) { // 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
}
if r1 == 0 { // WM_QUIT message
return
}
_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
// TODO move to init?
const (
msghandlerclass = "gomsghandler"
)
2014-04-09 10:13:35 -05:00
var (
// fron winuser.h; var because Go won't let me
_HWND_MESSAGE = - 3
)
2014-04-01 18:10:30 -05:00
func makeMessageHandler ( ) ( hwnd _HWND , err error ) {
wc := & _WNDCLASS {
lpszClassName : uintptr ( unsafe . Pointer ( syscall . StringToUTF16Ptr ( msghandlerclass ) ) ) ,
lpfnWndProc : syscall . NewCallback ( messageHandlerWndProc ) ,
hInstance : hInstance ,
hIcon : icon ,
hCursor : cursor ,
hbrBackground : _HBRUSH ( _COLOR_BTNFACE + 1 ) ,
}
r1 , _ , err := _registerClass . Call ( uintptr ( unsafe . Pointer ( wc ) ) )
if r1 == 0 { // failure
return _HWND ( _NULL ) , fmt . Errorf ( "error registering the class of the invisible window for handling events: %v" , err )
}
r1 , _ , err = _createWindowEx . Call (
uintptr ( 0 ) ,
uintptr ( unsafe . Pointer ( syscall . StringToUTF16Ptr ( msghandlerclass ) ) ) ,
2014-04-28 12:06:24 -05:00
uintptr ( unsafe . Pointer ( syscall . StringToUTF16Ptr ( "ui package message window" ) ) ) ,
2014-04-01 18:10:30 -05:00
uintptr ( 0 ) ,
uintptr ( _CW_USEDEFAULT ) ,
uintptr ( _CW_USEDEFAULT ) ,
uintptr ( _CW_USEDEFAULT ) ,
uintptr ( _CW_USEDEFAULT ) ,
2014-04-09 10:13:35 -05:00
uintptr ( _HWND_MESSAGE ) ,
2014-04-01 18:10:30 -05:00
uintptr ( _NULL ) ,
uintptr ( hInstance ) ,
uintptr ( _NULL ) )
if r1 == 0 { // failure
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 :
m := ( * uimsg ) ( unsafe . Pointer ( lParam ) )
r1 , _ , err := m . call . Call ( m . p ... )
m . ret <- uiret {
ret : r1 ,
err : err ,
}
return 0
case msgQuit :
// does not return a value according to MSDN
_postQuitMessage . Call ( 0 )
return 0
}
r1 , _ , _ := defWindowProc . Call (
uintptr ( hwnd ) ,
uintptr ( uMsg ) ,
uintptr ( wParam ) ,
uintptr ( lParam ) )
return _LRESULT ( r1 )
}