Commit | Line | Data |
---|---|---|
a09e091a JB |
1 | /* |
2 | *Copyright (C) 2003-2004 Harold L Hunt II All Rights Reserved. | |
3 | *Copyright (C) Colin Harrison 2005-2008 | |
4 | * | |
5 | *Permission is hereby granted, free of charge, to any person obtaining | |
6 | * a copy of this software and associated documentation files (the | |
7 | *"Software"), to deal in the Software without restriction, including | |
8 | *without limitation the rights to use, copy, modify, merge, publish, | |
9 | *distribute, sublicense, and/or sell copies of the Software, and to | |
10 | *permit persons to whom the Software is furnished to do so, subject to | |
11 | *the following conditions: | |
12 | * | |
13 | *The above copyright notice and this permission notice shall be | |
14 | *included in all copies or substantial portions of the Software. | |
15 | * | |
16 | *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
17 | *EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
18 | *MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
19 | *NONINFRINGEMENT. IN NO EVENT SHALL HAROLD L HUNT II BE LIABLE FOR | |
20 | *ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
21 | *CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
22 | *WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
23 | * | |
24 | *Except as contained in this notice, the name of the copyright holder(s) | |
25 | *and author(s) shall not be used in advertising or otherwise to promote | |
26 | *the sale, use or other dealings in this Software without prior written | |
27 | *authorization from the copyright holder(s) and author(s). | |
28 | * | |
29 | * Authors: Harold L Hunt II | |
30 | * Colin Harrison | |
31 | */ | |
32 | ||
33 | #ifdef HAVE_XWIN_CONFIG_H | |
34 | #include <xwin-config.h> | |
35 | #endif | |
36 | #include <sys/types.h> | |
37 | #include <sys/time.h> | |
38 | #include "winclipboard.h" | |
39 | #include "misc.h" | |
40 | ||
41 | /* | |
42 | * Constants | |
43 | */ | |
44 | ||
45 | #define WIN_POLL_TIMEOUT 1 | |
46 | ||
47 | /* | |
48 | * References to external symbols | |
49 | */ | |
50 | ||
51 | extern void *g_pClipboardDisplay; | |
52 | extern Window g_iClipboardWindow; | |
53 | extern Atom g_atomLastOwnedSelection; | |
54 | ||
55 | /* | |
56 | * Process X events up to specified timeout | |
57 | */ | |
58 | ||
59 | static int | |
60 | winProcessXEventsTimeout(HWND hwnd, int iWindow, Display * pDisplay, | |
61 | Bool fUseUnicode, int iTimeoutSec) | |
62 | { | |
63 | int iConnNumber; | |
64 | struct timeval tv; | |
65 | int iReturn; | |
66 | DWORD dwStopTime = GetTickCount() + iTimeoutSec * 1000; | |
67 | ||
68 | winDebug("winProcessXEventsTimeout () - pumping X events for %d seconds\n", | |
69 | iTimeoutSec); | |
70 | ||
71 | /* Get our connection number */ | |
72 | iConnNumber = ConnectionNumber(pDisplay); | |
73 | ||
74 | /* Loop for X events */ | |
75 | while (1) { | |
76 | fd_set fdsRead; | |
77 | long remainingTime; | |
78 | ||
79 | /* We need to ensure that all pending events are processed */ | |
80 | XSync(pDisplay, FALSE); | |
81 | ||
82 | /* Setup the file descriptor set */ | |
83 | FD_ZERO(&fdsRead); | |
84 | FD_SET(iConnNumber, &fdsRead); | |
85 | ||
86 | /* Adjust timeout */ | |
87 | remainingTime = dwStopTime - GetTickCount(); | |
88 | tv.tv_sec = remainingTime / 1000; | |
89 | tv.tv_usec = (remainingTime % 1000) * 1000; | |
90 | winDebug("winProcessXEventsTimeout () - %d milliseconds left\n", | |
91 | remainingTime); | |
92 | ||
93 | /* Break out if no time left */ | |
94 | if (remainingTime <= 0) | |
95 | return WIN_XEVENTS_SUCCESS; | |
96 | ||
97 | /* Wait for an X event */ | |
98 | iReturn = select(iConnNumber + 1, /* Highest fds number */ | |
99 | &fdsRead, /* Read mask */ | |
100 | NULL, /* No write mask */ | |
101 | NULL, /* No exception mask */ | |
102 | &tv); /* Timeout */ | |
103 | if (iReturn < 0) { | |
104 | ErrorF("winProcessXEventsTimeout - Call to select () failed: %d. " | |
105 | "Bailing.\n", iReturn); | |
106 | break; | |
107 | } | |
108 | ||
109 | /* Branch on which descriptor became active */ | |
110 | if (FD_ISSET(iConnNumber, &fdsRead)) { | |
111 | /* Process X events */ | |
112 | /* Exit when we see that server is shutting down */ | |
113 | iReturn = winClipboardFlushXEvents(hwnd, | |
114 | iWindow, pDisplay, fUseUnicode); | |
115 | ||
116 | winDebug | |
117 | ("winProcessXEventsTimeout () - winClipboardFlushXEvents returned %d\n", | |
118 | iReturn); | |
119 | ||
120 | if (WIN_XEVENTS_NOTIFY == iReturn) { | |
121 | /* Bail out if notify processed */ | |
122 | return iReturn; | |
123 | } | |
124 | } | |
125 | else { | |
126 | winDebug("winProcessXEventsTimeout - Spurious wake\n"); | |
127 | } | |
128 | } | |
129 | ||
130 | return WIN_XEVENTS_SUCCESS; | |
131 | } | |
132 | ||
133 | /* | |
134 | * Process a given Windows message | |
135 | */ | |
136 | ||
137 | LRESULT CALLBACK | |
138 | winClipboardWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) | |
139 | { | |
140 | static HWND s_hwndNextViewer; | |
141 | static Bool s_fCBCInitialized; | |
142 | ||
143 | /* Branch on message type */ | |
144 | switch (message) { | |
145 | case WM_DESTROY: | |
146 | { | |
147 | winDebug("winClipboardWindowProc - WM_DESTROY\n"); | |
148 | ||
149 | /* Remove ourselves from the clipboard chain */ | |
150 | ChangeClipboardChain(hwnd, s_hwndNextViewer); | |
151 | ||
152 | s_hwndNextViewer = NULL; | |
153 | ||
154 | PostQuitMessage(0); | |
155 | } | |
156 | return 0; | |
157 | ||
158 | case WM_CREATE: | |
159 | { | |
160 | HWND first, next; | |
161 | DWORD error_code = 0; | |
162 | ||
163 | winDebug("winClipboardWindowProc - WM_CREATE\n"); | |
164 | ||
165 | first = GetClipboardViewer(); /* Get handle to first viewer in chain. */ | |
166 | if (first == hwnd) | |
167 | return 0; /* Make sure it's not us! */ | |
168 | /* Add ourselves to the clipboard viewer chain */ | |
169 | next = SetClipboardViewer(hwnd); | |
170 | error_code = GetLastError(); | |
171 | if (SUCCEEDED(error_code) && (next == first)) /* SetClipboardViewer must have succeeded, and the handle */ | |
172 | s_hwndNextViewer = next; /* it returned must have been the first window in the chain */ | |
173 | else | |
174 | s_fCBCInitialized = FALSE; | |
175 | } | |
176 | return 0; | |
177 | ||
178 | case WM_CHANGECBCHAIN: | |
179 | { | |
180 | winDebug("winClipboardWindowProc - WM_CHANGECBCHAIN: wParam(%x) " | |
181 | "lParam(%x) s_hwndNextViewer(%x)\n", | |
182 | wParam, lParam, s_hwndNextViewer); | |
183 | ||
184 | if ((HWND) wParam == s_hwndNextViewer) { | |
185 | s_hwndNextViewer = (HWND) lParam; | |
186 | if (s_hwndNextViewer == hwnd) { | |
187 | s_hwndNextViewer = NULL; | |
188 | winErrorFVerb(1, "winClipboardWindowProc - WM_CHANGECBCHAIN: " | |
189 | "attempted to set next window to ourselves."); | |
190 | } | |
191 | } | |
192 | else if (s_hwndNextViewer) | |
193 | SendMessage(s_hwndNextViewer, message, wParam, lParam); | |
194 | ||
195 | } | |
196 | winDebug("winClipboardWindowProc - WM_CHANGECBCHAIN: Exit\n"); | |
197 | return 0; | |
198 | ||
199 | case WM_WM_REINIT: | |
200 | { | |
201 | /* Ensure that we're in the clipboard chain. Some apps, | |
202 | * WinXP's remote desktop for one, don't play nice with the | |
203 | * chain. This message is called whenever we receive a | |
204 | * WM_ACTIVATEAPP message to ensure that we continue to | |
205 | * receive clipboard messages. | |
206 | * | |
207 | * It might be possible to detect if we're still in the chain | |
208 | * by calling SendMessage (GetClipboardViewer(), | |
209 | * WM_DRAWCLIPBOARD, 0, 0); and then seeing if we get the | |
210 | * WM_DRAWCLIPBOARD message. That, however, might be more | |
211 | * expensive than just putting ourselves back into the chain. | |
212 | */ | |
213 | ||
214 | HWND first, next; | |
215 | DWORD error_code = 0; | |
216 | ||
217 | winDebug("winClipboardWindowProc - WM_WM_REINIT: Enter\n"); | |
218 | ||
219 | first = GetClipboardViewer(); /* Get handle to first viewer in chain. */ | |
220 | if (first == hwnd) | |
221 | return 0; /* Make sure it's not us! */ | |
222 | winDebug(" WM_WM_REINIT: Replacing us(%x) with %x at head " | |
223 | "of chain\n", hwnd, s_hwndNextViewer); | |
224 | s_fCBCInitialized = FALSE; | |
225 | ChangeClipboardChain(hwnd, s_hwndNextViewer); | |
226 | s_hwndNextViewer = NULL; | |
227 | s_fCBCInitialized = FALSE; | |
228 | winDebug(" WM_WM_REINIT: Putting us back at head of chain.\n"); | |
229 | first = GetClipboardViewer(); /* Get handle to first viewer in chain. */ | |
230 | if (first == hwnd) | |
231 | return 0; /* Make sure it's not us! */ | |
232 | next = SetClipboardViewer(hwnd); | |
233 | error_code = GetLastError(); | |
234 | if (SUCCEEDED(error_code) && (next == first)) /* SetClipboardViewer must have succeeded, and the handle */ | |
235 | s_hwndNextViewer = next; /* it returned must have been the first window in the chain */ | |
236 | else | |
237 | s_fCBCInitialized = FALSE; | |
238 | } | |
239 | winDebug("winClipboardWindowProc - WM_WM_REINIT: Exit\n"); | |
240 | return 0; | |
241 | ||
242 | case WM_DRAWCLIPBOARD: | |
243 | { | |
244 | static Atom atomClipboard; | |
245 | static int generation; | |
246 | static Bool s_fProcessingDrawClipboard = FALSE; | |
247 | Display *pDisplay = g_pClipboardDisplay; | |
248 | Window iWindow = g_iClipboardWindow; | |
249 | int iReturn; | |
250 | ||
251 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Enter\n"); | |
252 | ||
253 | if (generation != serverGeneration) { | |
254 | generation = serverGeneration; | |
255 | atomClipboard = XInternAtom(pDisplay, "CLIPBOARD", False); | |
256 | } | |
257 | ||
258 | /* | |
259 | * We've occasionally seen a loop in the clipboard chain. | |
260 | * Try and fix it on the first hint of recursion. | |
261 | */ | |
262 | if (!s_fProcessingDrawClipboard) { | |
263 | s_fProcessingDrawClipboard = TRUE; | |
264 | } | |
265 | else { | |
266 | /* Attempt to break the nesting by getting out of the chain, twice?, and then fix and bail */ | |
267 | s_fCBCInitialized = FALSE; | |
268 | ChangeClipboardChain(hwnd, s_hwndNextViewer); | |
269 | winFixClipboardChain(); | |
270 | winErrorFVerb(1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
271 | "Nested calls detected. Re-initing.\n"); | |
272 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n"); | |
273 | s_fProcessingDrawClipboard = FALSE; | |
274 | return 0; | |
275 | } | |
276 | ||
277 | /* Bail on first message */ | |
278 | if (!s_fCBCInitialized) { | |
279 | s_fCBCInitialized = TRUE; | |
280 | s_fProcessingDrawClipboard = FALSE; | |
281 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n"); | |
282 | return 0; | |
283 | } | |
284 | ||
285 | /* | |
286 | * NOTE: We cannot bail out when NULL == GetClipboardOwner () | |
287 | * because some applications deal with the clipboard in a manner | |
288 | * that causes the clipboard owner to be NULL when they are in | |
289 | * fact taking ownership. One example of this is the Win32 | |
290 | * native compile of emacs. | |
291 | */ | |
292 | ||
293 | /* Bail when we still own the clipboard */ | |
294 | if (hwnd == GetClipboardOwner()) { | |
295 | ||
296 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
297 | "We own the clipboard, returning.\n"); | |
298 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n"); | |
299 | s_fProcessingDrawClipboard = FALSE; | |
300 | if (s_hwndNextViewer) | |
301 | SendMessage(s_hwndNextViewer, message, wParam, lParam); | |
302 | return 0; | |
303 | } | |
304 | ||
305 | /* | |
306 | * Do not take ownership of the X11 selections when something | |
307 | * other than CF_TEXT or CF_UNICODETEXT has been copied | |
308 | * into the Win32 clipboard. | |
309 | */ | |
310 | if (!IsClipboardFormatAvailable(CF_TEXT) | |
311 | && !IsClipboardFormatAvailable(CF_UNICODETEXT)) { | |
312 | ||
313 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
314 | "Clipboard does not contain CF_TEXT nor " | |
315 | "CF_UNICODETEXT.\n"); | |
316 | ||
317 | /* | |
318 | * We need to make sure that the X Server has processed | |
319 | * previous XSetSelectionOwner messages. | |
320 | */ | |
321 | XSync(pDisplay, FALSE); | |
322 | ||
323 | winDebug("winClipboardWindowProc - XSync done.\n"); | |
324 | ||
325 | /* Release PRIMARY selection if owned */ | |
326 | iReturn = XGetSelectionOwner(pDisplay, XA_PRIMARY); | |
327 | if (iReturn == g_iClipboardWindow) { | |
328 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
329 | "PRIMARY selection is owned by us.\n"); | |
330 | XSetSelectionOwner(pDisplay, XA_PRIMARY, None, CurrentTime); | |
331 | } | |
332 | else if (BadWindow == iReturn || BadAtom == iReturn) | |
333 | winErrorFVerb(1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
334 | "XGetSelection failed for PRIMARY: %d\n", | |
335 | iReturn); | |
336 | ||
337 | /* Release CLIPBOARD selection if owned */ | |
338 | iReturn = XGetSelectionOwner(pDisplay, atomClipboard); | |
339 | if (iReturn == g_iClipboardWindow) { | |
340 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
341 | "CLIPBOARD selection is owned by us.\n"); | |
342 | XSetSelectionOwner(pDisplay, atomClipboard, None, CurrentTime); | |
343 | } | |
344 | else if (BadWindow == iReturn || BadAtom == iReturn) | |
345 | winErrorFVerb(1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
346 | "XGetSelection failed for CLIPBOARD: %d\n", | |
347 | iReturn); | |
348 | ||
349 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n"); | |
350 | s_fProcessingDrawClipboard = FALSE; | |
351 | if (s_hwndNextViewer) | |
352 | SendMessage(s_hwndNextViewer, message, wParam, lParam); | |
353 | return 0; | |
354 | } | |
355 | ||
356 | /* Reassert ownership of PRIMARY */ | |
357 | iReturn = XSetSelectionOwner(pDisplay, | |
358 | XA_PRIMARY, iWindow, CurrentTime); | |
359 | if (iReturn == BadAtom || iReturn == BadWindow || | |
360 | XGetSelectionOwner(pDisplay, XA_PRIMARY) != iWindow) { | |
361 | winErrorFVerb(1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
362 | "Could not reassert ownership of PRIMARY\n"); | |
363 | } | |
364 | else { | |
365 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
366 | "Reasserted ownership of PRIMARY\n"); | |
367 | } | |
368 | ||
369 | /* Reassert ownership of the CLIPBOARD */ | |
370 | iReturn = XSetSelectionOwner(pDisplay, | |
371 | atomClipboard, iWindow, CurrentTime); | |
372 | ||
373 | if (iReturn == BadAtom || iReturn == BadWindow || | |
374 | XGetSelectionOwner(pDisplay, atomClipboard) != iWindow) { | |
375 | winErrorFVerb(1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
376 | "Could not reassert ownership of CLIPBOARD\n"); | |
377 | } | |
378 | else { | |
379 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - " | |
380 | "Reasserted ownership of CLIPBOARD\n"); | |
381 | } | |
382 | ||
383 | /* Flush the pending SetSelectionOwner event now */ | |
384 | XFlush(pDisplay); | |
385 | ||
386 | s_fProcessingDrawClipboard = FALSE; | |
387 | } | |
388 | winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n"); | |
389 | /* Pass the message on the next window in the clipboard viewer chain */ | |
390 | if (s_hwndNextViewer) | |
391 | SendMessage(s_hwndNextViewer, message, wParam, lParam); | |
392 | return 0; | |
393 | ||
394 | case WM_DESTROYCLIPBOARD: | |
395 | /* | |
396 | * NOTE: Intentionally do nothing. | |
397 | * Changes in the Win32 clipboard are handled by WM_DRAWCLIPBOARD | |
398 | * above. We only process this message to conform to the specs | |
399 | * for delayed clipboard rendering in Win32. You might think | |
400 | * that we need to release ownership of the X11 selections, but | |
401 | * we do not, because a WM_DRAWCLIPBOARD message will closely | |
402 | * follow this message and reassert ownership of the X11 | |
403 | * selections, handling the issue for us. | |
404 | */ | |
405 | winDebug("winClipboardWindowProc - WM_DESTROYCLIPBOARD - Ignored.\n"); | |
406 | return 0; | |
407 | ||
408 | case WM_RENDERFORMAT: | |
409 | case WM_RENDERALLFORMATS: | |
410 | { | |
411 | int iReturn; | |
412 | Display *pDisplay = g_pClipboardDisplay; | |
413 | Window iWindow = g_iClipboardWindow; | |
414 | Bool fConvertToUnicode; | |
415 | ||
416 | winDebug("winClipboardWindowProc - WM_RENDER*FORMAT - Hello.\n"); | |
417 | ||
418 | /* Flag whether to convert to Unicode or not */ | |
419 | if (message == WM_RENDERALLFORMATS) | |
420 | fConvertToUnicode = FALSE; | |
421 | else | |
422 | fConvertToUnicode = (CF_UNICODETEXT == wParam); | |
423 | ||
424 | /* Request the selection contents */ | |
425 | iReturn = XConvertSelection(pDisplay, | |
426 | g_atomLastOwnedSelection, | |
427 | XInternAtom(pDisplay, | |
428 | "COMPOUND_TEXT", False), | |
429 | XInternAtom(pDisplay, | |
430 | "CYGX_CUT_BUFFER", False), | |
431 | iWindow, CurrentTime); | |
432 | if (iReturn == BadAtom || iReturn == BadWindow) { | |
433 | winErrorFVerb(1, "winClipboardWindowProc - WM_RENDER*FORMAT - " | |
434 | "XConvertSelection () failed\n"); | |
435 | break; | |
436 | } | |
437 | ||
438 | /* Special handling for WM_RENDERALLFORMATS */ | |
439 | if (message == WM_RENDERALLFORMATS) { | |
440 | /* We must open and empty the clipboard */ | |
441 | ||
442 | /* Close clipboard if we have it open already */ | |
443 | if (GetOpenClipboardWindow() == hwnd) { | |
444 | CloseClipboard(); | |
445 | } | |
446 | ||
447 | if (!OpenClipboard(hwnd)) { | |
448 | winErrorFVerb(1, "winClipboardWindowProc - WM_RENDER*FORMATS - " | |
449 | "OpenClipboard () failed: %08x\n", | |
450 | GetLastError()); | |
451 | break; | |
452 | } | |
453 | ||
454 | if (!EmptyClipboard()) { | |
455 | winErrorFVerb(1, "winClipboardWindowProc - WM_RENDER*FORMATS - " | |
456 | "EmptyClipboard () failed: %08x\n", | |
457 | GetLastError()); | |
458 | break; | |
459 | } | |
460 | } | |
461 | ||
462 | /* Process the SelectionNotify event */ | |
463 | iReturn = winProcessXEventsTimeout(hwnd, | |
464 | iWindow, | |
465 | pDisplay, | |
466 | fConvertToUnicode, WIN_POLL_TIMEOUT); | |
467 | ||
468 | /* | |
469 | * The last call to winProcessXEventsTimeout | |
470 | * from above had better have seen a notify event, or else we | |
471 | * are dealing with a buggy or old X11 app. In these cases we | |
472 | * have to paste some fake data to the Win32 clipboard to | |
473 | * satisfy the requirement that we write something to it. | |
474 | */ | |
475 | if (WIN_XEVENTS_NOTIFY != iReturn) { | |
476 | /* Paste no data, to satisfy required call to SetClipboardData */ | |
477 | SetClipboardData(CF_UNICODETEXT, NULL); | |
478 | SetClipboardData(CF_TEXT, NULL); | |
479 | ||
480 | ErrorF | |
481 | ("winClipboardWindowProc - timed out waiting for WIN_XEVENTS_NOTIFY\n"); | |
482 | } | |
483 | ||
484 | /* Special handling for WM_RENDERALLFORMATS */ | |
485 | if (message == WM_RENDERALLFORMATS) { | |
486 | /* We must close the clipboard */ | |
487 | ||
488 | if (!CloseClipboard()) { | |
489 | winErrorFVerb(1, | |
490 | "winClipboardWindowProc - WM_RENDERALLFORMATS - " | |
491 | "CloseClipboard () failed: %08x\n", | |
492 | GetLastError()); | |
493 | break; | |
494 | } | |
495 | } | |
496 | ||
497 | winDebug("winClipboardWindowProc - WM_RENDER*FORMAT - Returning.\n"); | |
498 | return 0; | |
499 | } | |
500 | } | |
501 | ||
502 | /* Let Windows perform default processing for unhandled messages */ | |
503 | return DefWindowProc(hwnd, message, wParam, lParam); | |
504 | } | |
505 | ||
506 | /* | |
507 | * Process any pending Windows messages | |
508 | */ | |
509 | ||
510 | BOOL | |
511 | winClipboardFlushWindowsMessageQueue(HWND hwnd) | |
512 | { | |
513 | MSG msg; | |
514 | ||
515 | /* Flush the messaging window queue */ | |
516 | /* NOTE: Do not pass the hwnd of our messaging window to PeekMessage, | |
517 | * as this will filter out many non-window-specific messages that | |
518 | * are sent to our thread, such as WM_QUIT. | |
519 | */ | |
520 | while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { | |
521 | /* Dispatch the message if not WM_QUIT */ | |
522 | if (msg.message == WM_QUIT) | |
523 | return FALSE; | |
524 | else | |
525 | DispatchMessage(&msg); | |
526 | } | |
527 | ||
528 | return TRUE; | |
529 | } |