summaryrefslogtreecommitdiff
path: root/redo/uitask_windows.c
blob: 25f7da9b7df7c19890d01880e969f9259957611b (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
// 17 july 2014

#include "winapi_windows.h"
#include "_cgo_export.h"
#include "modalqueue.h"

// note that this includes the terminating '\0'
// this also assumes WC_TABCONTROL is longer than areaWindowClass
#define NCLASSNAME (sizeof WC_TABCONTROL / sizeof WC_TABCONTROL[0])

void uimsgloop_area(HWND active, HWND focus, MSG *msg)
{
	MSG copy;

	copy = *msg;
	switch (copy.message) {
	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:			// Alt+[anything] and F10 send these instead
		copy.message = msgAreaKeyDown;
		break;
	case WM_KEYUP:
	case WM_SYSKEYUP:
		copy.message = msgAreaKeyUp;
		break;
	default:
		goto notkey;
	}
	// if we handled the key, don't do the default behavior
	// don't call TranslateMessage(); we do our own keyboard handling
	if (DispatchMessage(&copy) != FALSE)
		return;
	// TODO move this under notkey?
	if (IsDialogMessage(active, msg) != 0)
		return;
notkey:
	DispatchMessage(msg);
}

void uimsgloop_tab(HWND active, HWND focus, MSG *msg)
{
	BOOL hasChildren;
	BOOL idm;

	// THIS BIT IS IMPORTANT: if the current tab has no children, then there will be no children left in the dialog to tab to, and IsDialogMessageW() will loop forever
	hasChildren = SendMessageW(focus, msgTabCurrentTabHasChildren, 0, 0);
	if (hasChildren)
		tabEnterChildren(focus);
	idm = IsDialogMessageW(active, msg);
	if (hasChildren)
		tabLeaveChildren(focus);
	if (idm != 0)
		return;
	TranslateMessage(msg);
	DispatchMessage(msg);
}

void uimsgloop_else(MSG *msg)
{
	TranslateMessage(msg);
	DispatchMessage(msg);
}

void uimsgloop(void)
{
	MSG msg;
	int res;
	HWND active, focus;
	WCHAR classchk[NCLASSNAME];
	BOOL dodlgmessage;

	for (;;) {
		SetLastError(0);
		res = GetMessageW(&msg, NULL, 0, 0);
		if (res < 0)
			xpanic("error calling GetMessage()", GetLastError());
		if (res == 0)		// WM_QUIT
			break;
		active = GetActiveWindow();
		if (active == NULL) {
			uimsgloop_else(&msg);
			continue;
		}

		// bit of logic involved here:
		// we don't want dialog messages passed into Areas, so we don't call IsDialogMessageW() there
		// as for Tabs, we can't have both WS_TABSTOP and WS_EX_CONTROLPARENT set at the same time, so we hotswap the two styles to get the behavior we want
		// theoretically we could use the class atom to avoid a wcscmp()
		// however, raymond chen advises against this - http://blogs.msdn.com/b/oldnewthing/archive/2004/10/11/240744.aspx (and we're not in control of the Tab class, before you say anything)
		// we could also theoretically just send msgAreaDefocuses directly, but what DefWindowProc() does to a WM_APP message is undocumented
		focus = GetFocus();
		if (focus != NULL) {
			if (GetClassNameW(focus, classchk, NCLASSNAME) == 0)
				xpanic("error getting name of focused window class for Area check", GetLastError());
			if (wcscmp(classchk, areaWindowClass) == 0) {
				uimsgloop_area(active, focus, &msg);
				continue;
			} else if (wcscmp(classchk, WC_TABCONTROL) == 0) {
				uimsgloop_tab(active, focus, &msg);
				continue;
			}
			// else fall through
		}

		if (IsDialogMessage(active, &msg) != 0)
			continue;
		uimsgloop_else(&msg);
	}
}

void issue(void *request)
{
	SetLastError(0);
	if (PostMessageW(msgwin, msgRequest, 0, (LPARAM) request) == 0)
		xpanic("error issuing request", GetLastError());
}

HWND msgwin;

#define msgwinclass L"gouimsgwin"

static BOOL CALLBACK beginEndModalAll(HWND hwnd, LPARAM lParam)
{
	if (hwnd != msgwin)
		SendMessageW(hwnd, (UINT) lParam, 0, 0);
	return TRUE;
}

static LRESULT CALLBACK msgwinproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	LRESULT shared;
	size_t i;

	if (sharedWndProc(hwnd, uMsg, wParam, lParam, &shared))
		return shared;
	switch (uMsg) {
	case WM_CREATE:
		// initial
		makeCheckboxImageList(hwnd);
		return 0;
	// TODO respond to WM_THEMECHANGED
	case msgRequest:
		// in modal?
		if (!queueIfModal((void *) lParam))
			// nope, we can run now
			doissue((void *) lParam);
		return 0;
	case msgBeginModal:
		beginModal();
		EnumThreadWindows(GetCurrentThreadId(), beginEndModalAll, msgBeginModal);
		return 0;
	case msgEndModal:
		endModal();
		EnumThreadWindows(GetCurrentThreadId(), beginEndModalAll, msgEndModal);
		return 0;
	default:
		return DefWindowProcW(hwnd, uMsg, wParam, lParam);
	}
	xmissedmsg("message-only", "msgwinproc()", uMsg);
	return 0;		// unreachable
}

DWORD makemsgwin(char **errmsg)
{
	WNDCLASSW wc;

	ZeroMemory(&wc, sizeof (WNDCLASSW));
	wc.lpfnWndProc = msgwinproc;
	wc.hInstance = hInstance;
	wc.hIcon = hDefaultIcon;
	wc.hCursor = hArrowCursor;
	wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
	wc.lpszClassName = msgwinclass;
	if (RegisterClassW(&wc) == 0) {
		*errmsg = "error registering message-only window classs";
		return GetLastError();
	}
	msgwin = CreateWindowExW(
		0,
		msgwinclass, L"package ui message-only window",
		0,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		HWND_MESSAGE, NULL, hInstance, NULL);
	if (msgwin == NULL) {
		*errmsg = "error creating message-only window";
		return GetLastError();
	}
	return 0;
}