summaryrefslogtreecommitdiff
path: root/prevlib/uitask_windows.go
blob: 732e6410b5e54b40aca5bf1ea6d1af0531eee307 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// 11 february 2014

package ui

import (
	"fmt"
	"syscall"
	"unsafe"
)

/*
TODO rewrite this comment block

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)
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)

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)
this happened when scrolling Areas (as scrolling is modal; see http://blogs.msdn.com/b/oldnewthing/archive/2005/04/27/412565.aspx)

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.
*/

var msghwnd _HWND

const (
	msgQuit = _WM_APP + iota + 1			// + 1 just to be safe
	msgSetAreaSize
	msgRepaintAll
	msgCreateWindow
)

type uitaskParams struct {
	window	*Window		// createWindow
	control	Control		// createWindow
	show	bool			// createWindow
}

// SendMessage() won't return unti lthe deed is done, even if the deed is on another thread
// SendMessage() does a thread switch if necessary
// this also means we don't have to worry about the uitaskParams object being garbage collected

func (_uitask) createWindow(w *Window, c Control, s bool) {
	uc := &uitaskParams{
		window:	w,
		control:	c,
		show:	s,
	}
	_sendMessage.Call(
		uintptr(msghwnd),
		msgCreateWindow,
		uintptr(0),
		uintptr(unsafe.Pointer(uc)))
}

func uiinit() error {
	err := doWindowsInit()
	if err != nil {
		return fmt.Errorf("error doing general Windows initialization: %v", err)
	}

	msghwnd, err = makeMessageHandler()
	if err != nil {
		return fmt.Errorf("error making invisible window for handling events: %v", err)
	}

	return nil
}

var (
	_postMessage = user32.NewProc("PostMessageW")
)

func ui() {
	go func() {
		<-Stop
		// PostMessage() so it gets handled after any events currently being processed complete
		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())
		}
	}()

	msgloop()
}

var (
	_dispatchMessage  = user32.NewProc("DispatchMessageW")
	_getActiveWindow		= user32.NewProc("GetActiveWindow")
	_getMessage       = user32.NewProc("GetMessageW")
	_isDialogMessage		= user32.NewProc("IsDialogMessageW")
	_postQuitMessage  = user32.NewProc("PostQuitMessage")
	_sendMessage      = user32.NewProc("SendMessageW")
	_translateMessage = user32.NewProc("TranslateMessage")
)

func msgloop() {
	var msg struct {
		hwnd    _HWND
		message uint32
		wParam  _WPARAM
		lParam  _LPARAM
		time    uint32
		pt      _POINT
	}

	for {
		r1, _, err := _getMessage.Call(
			uintptr(unsafe.Pointer(&msg)),
			uintptr(_NULL),
			uintptr(0),
			uintptr(0))
		if r1 == negConst(-1) { // error
			panic("error getting message in message loop: " + err.Error())
		}
		if r1 == 0 { // WM_QUIT message
			return
		}
		// this next bit handles tab stops
		r1, _, _ = _getActiveWindow.Call()
		r1, _, _ = _isDialogMessage.Call(
			r1,		// active window
			uintptr(unsafe.Pointer(&msg)))
		if r1 != 0 {
			continue
		}
		_translateMessage.Call(uintptr(unsafe.Pointer(&msg)))
		_dispatchMessage.Call(uintptr(unsafe.Pointer(&msg)))
	}
}

var (
	msghandlerclass = toUTF16("gomsghandler")
	msghandlertitle = toUTF16("ui package message window")
)

func makeMessageHandler() (hwnd _HWND, err error) {
	wc := &_WNDCLASS{
		lpszClassName: utf16ToArg(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),
		utf16ToArg(msghandlerclass),
		utf16ToArg(msghandlertitle),
		uintptr(0),
		negConst(_CW_USEDEFAULT),
		negConst(_CW_USEDEFAULT),
		negConst(_CW_USEDEFAULT),
		negConst(_CW_USEDEFAULT),
		// 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),
		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 msgQuit:
		// does not return a value according to MSDN
		_postQuitMessage.Call(0)
		return 0
	case msgCreateWindow:
		uc := (*uitaskParams)(unsafe.Pointer(lParam))
		uc.window.create(uc.control, uc.show)
		return 0
	}
	return defWindowProc(hwnd, uMsg, wParam, lParam)
}