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
37 /*
38 * Including any server header might define the macro _XSERVER64 on 64 bit machines.
39 * That macro must _NOT_ be defined for Xlib client code, otherwise bad things happen.
40 * So let's undef that macro if necessary.
41 */
42 #ifdef _XSERVER64
43 #undef _XSERVER64
44 #endif
45
46 #include <sys/types.h>
47 #include <sys/time.h>
48 #include <limits.h>
49
50 #include <X11/Xatom.h>
51
52 #include "internal.h"
53 #include "winclipboard.h"
54
55 /*
56 * Constants
57 */
58
59 #define WIN_POLL_TIMEOUT 1
60
61 #ifndef WM_CLIPBOARDUPDATE
62 #define WM_CLIPBOARDUPDATE 0x031D
63 #endif
64
65 /*
66 * Process X events up to specified timeout
67 */
68
69 static int
winProcessXEventsTimeout(HWND hwnd,Window iWindow,Display * pDisplay,ClipboardConversionData * data,ClipboardAtoms * atoms,int iTimeoutSec)70 winProcessXEventsTimeout(HWND hwnd, Window iWindow, Display * pDisplay,
71 ClipboardConversionData *data, ClipboardAtoms *atoms, int iTimeoutSec)
72 {
73 int iConnNumber;
74 struct timeval tv;
75 int iReturn;
76 DWORD dwStopTime = GetTickCount() + iTimeoutSec * 1000;
77
78 winDebug("winProcessXEventsTimeout () - pumping X events for %d seconds\n",
79 iTimeoutSec);
80
81 /* Get our connection number */
82 iConnNumber = ConnectionNumber(pDisplay);
83
84 /* Loop for X events */
85 while (1) {
86 fd_set fdsRead;
87 long remainingTime;
88
89 /* Process X events */
90 iReturn = winClipboardFlushXEvents(hwnd, iWindow, pDisplay, data, atoms);
91
92 winDebug("winProcessXEventsTimeout () - winClipboardFlushXEvents returned %d\n", iReturn);
93
94 if ((WIN_XEVENTS_NOTIFY_DATA == iReturn) || (WIN_XEVENTS_NOTIFY_TARGETS == iReturn) || (WIN_XEVENTS_FAILED == iReturn)) {
95 /* Bail out */
96 return iReturn;
97 }
98
99 /* We need to ensure that all pending requests are sent */
100 XFlush(pDisplay);
101
102 /* Setup the file descriptor set */
103 FD_ZERO(&fdsRead);
104 FD_SET(iConnNumber, &fdsRead);
105
106 /* Adjust timeout */
107 remainingTime = dwStopTime - GetTickCount();
108 tv.tv_sec = remainingTime / 1000;
109 tv.tv_usec = (remainingTime % 1000) * 1000;
110 winDebug("winProcessXEventsTimeout () - %ld milliseconds left\n",
111 remainingTime);
112
113 /* Break out if no time left */
114 if (remainingTime <= 0)
115 return WIN_XEVENTS_SUCCESS;
116
117 /* Wait for an X event */
118 iReturn = select(iConnNumber + 1, /* Highest fds number */
119 &fdsRead, /* Read mask */
120 NULL, /* No write mask */
121 NULL, /* No exception mask */
122 &tv); /* Timeout */
123 if (iReturn < 0) {
124 ErrorF("winProcessXEventsTimeout - Call to select () failed: %d. "
125 "Bailing.\n", iReturn);
126 break;
127 }
128
129 if (!FD_ISSET(iConnNumber, &fdsRead)) {
130 winDebug("winProcessXEventsTimeout - Spurious wake, select() returned %d\n", iReturn);
131 }
132 }
133
134 return WIN_XEVENTS_SUCCESS;
135 }
136
137 /*
138 * Process a given Windows message
139 */
140
141 LRESULT CALLBACK
winClipboardWindowProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)142 winClipboardWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
143 {
144 static HWND s_hwndNextViewer;
145 static Bool s_fCBCInitialized;
146 static Display *pDisplay;
147 static Window iWindow;
148 static ClipboardAtoms *atoms;
149 static Bool fRunning;
150
151 /* Branch on message type */
152 switch (message) {
153 case WM_DESTROY:
154 {
155 winDebug("winClipboardWindowProc - WM_DESTROY\n");
156
157 if (g_fHasModernClipboardApi)
158 {
159 /* Remove clipboard listener */
160 g_fpRemoveClipboardFormatListener(hwnd);
161 }
162 else
163 {
164 /* Remove ourselves from the clipboard chain */
165 ChangeClipboardChain(hwnd, s_hwndNextViewer);
166 }
167
168 s_hwndNextViewer = NULL;
169 }
170 return 0;
171
172 case WM_WM_QUIT:
173 {
174 winDebug("winClipboardWindowProc - WM_WM_QUIT\n");
175 fRunning = FALSE;
176 PostQuitMessage(0);
177 }
178 return 0;
179
180 case WM_CREATE:
181 {
182 ClipboardWindowCreationParams *cwcp = (ClipboardWindowCreationParams *)((CREATESTRUCT *)lParam)->lpCreateParams;
183
184 winDebug("winClipboardWindowProc - WM_CREATE\n");
185
186 pDisplay = cwcp->pClipboardDisplay;
187 iWindow = cwcp->iClipboardWindow;
188 atoms = cwcp->atoms;
189 fRunning = TRUE;
190
191 if (g_fHasModernClipboardApi)
192 {
193 g_fpAddClipboardFormatListener(hwnd);
194 }
195 else
196 {
197 HWND first, next;
198 DWORD error_code = 0;
199
200 first = GetClipboardViewer(); /* Get handle to first viewer in chain. */
201 if (first == hwnd)
202 return 0; /* Make sure it's not us! */
203 /* Add ourselves to the clipboard viewer chain */
204 next = SetClipboardViewer(hwnd);
205 error_code = GetLastError();
206 if (SUCCEEDED(error_code) && (next == first)) /* SetClipboardViewer must have succeeded, and the handle */
207 s_hwndNextViewer = next; /* it returned must have been the first window in the chain */
208 else
209 s_fCBCInitialized = FALSE;
210 }
211 }
212 return 0;
213
214 case WM_CHANGECBCHAIN:
215 {
216 winDebug("winClipboardWindowProc - WM_CHANGECBCHAIN: wParam(%p) "
217 "lParam(%p) s_hwndNextViewer(%p)\n",
218 (HWND)wParam, (HWND)lParam, s_hwndNextViewer);
219
220 if ((HWND) wParam == s_hwndNextViewer) {
221 s_hwndNextViewer = (HWND) lParam;
222 if (s_hwndNextViewer == hwnd) {
223 s_hwndNextViewer = NULL;
224 ErrorF("winClipboardWindowProc - WM_CHANGECBCHAIN: "
225 "attempted to set next window to ourselves.");
226 }
227 }
228 else if (s_hwndNextViewer)
229 SendMessage(s_hwndNextViewer, message, wParam, lParam);
230
231 }
232 winDebug("winClipboardWindowProc - WM_CHANGECBCHAIN: Exit\n");
233 return 0;
234
235 case WM_WM_REINIT:
236 {
237 /* Ensure that we're in the clipboard chain. Some apps,
238 * WinXP's remote desktop for one, don't play nice with the
239 * chain. This message is called whenever we receive a
240 * WM_ACTIVATEAPP message to ensure that we continue to
241 * receive clipboard messages.
242 *
243 * It might be possible to detect if we're still in the chain
244 * by calling SendMessage (GetClipboardViewer(),
245 * WM_DRAWCLIPBOARD, 0, 0); and then seeing if we get the
246 * WM_DRAWCLIPBOARD message. That, however, might be more
247 * expensive than just putting ourselves back into the chain.
248 */
249
250 HWND first, next;
251 DWORD error_code = 0;
252
253 winDebug("winClipboardWindowProc - WM_WM_REINIT: Enter\n");
254
255 if (g_fHasModernClipboardApi)
256 {
257 return 0;
258 }
259
260 first = GetClipboardViewer(); /* Get handle to first viewer in chain. */
261 if (first == hwnd)
262 return 0; /* Make sure it's not us! */
263 winDebug(" WM_WM_REINIT: Replacing us(%p) with %p at head "
264 "of chain\n", hwnd, s_hwndNextViewer);
265 s_fCBCInitialized = FALSE;
266 ChangeClipboardChain(hwnd, s_hwndNextViewer);
267 s_hwndNextViewer = NULL;
268 s_fCBCInitialized = FALSE;
269 winDebug(" WM_WM_REINIT: Putting us back at head of chain.\n");
270 first = GetClipboardViewer(); /* Get handle to first viewer in chain. */
271 if (first == hwnd)
272 return 0; /* Make sure it's not us! */
273 next = SetClipboardViewer(hwnd);
274 error_code = GetLastError();
275 if (SUCCEEDED(error_code) && (next == first)) /* SetClipboardViewer must have succeeded, and the handle */
276 s_hwndNextViewer = next; /* it returned must have been the first window in the chain */
277 else
278 s_fCBCInitialized = FALSE;
279 }
280 winDebug("winClipboardWindowProc - WM_WM_REINIT: Exit\n");
281 return 0;
282
283 case WM_DRAWCLIPBOARD:
284 case WM_CLIPBOARDUPDATE:
285 {
286 static Bool s_fProcessingDrawClipboard = FALSE;
287 int iReturn;
288
289 if (message == WM_DRAWCLIPBOARD)
290 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Enter\n");
291 else
292 winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE: Enter\n");
293
294 if (!g_fHasModernClipboardApi)
295 {
296 /*
297 * We've occasionally seen a loop in the clipboard chain.
298 * Try and fix it on the first hint of recursion.
299 */
300 if (!s_fProcessingDrawClipboard) {
301 s_fProcessingDrawClipboard = TRUE;
302 }
303 else {
304 /* Attempt to break the nesting by getting out of the chain, twice?, and then fix and bail */
305 s_fCBCInitialized = FALSE;
306 ChangeClipboardChain(hwnd, s_hwndNextViewer);
307 winFixClipboardChain();
308 ErrorF("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
309 "Nested calls detected. Re-initing.\n");
310 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
311 s_fProcessingDrawClipboard = FALSE;
312 return 0;
313 }
314
315 /* Bail on first message */
316 if (!s_fCBCInitialized) {
317 s_fCBCInitialized = TRUE;
318 s_fProcessingDrawClipboard = FALSE;
319 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
320 return 0;
321 }
322 }
323
324 /*
325 * NOTE: We cannot bail out when NULL == GetClipboardOwner ()
326 * because some applications deal with the clipboard in a manner
327 * that causes the clipboard owner to be NULL when they are in
328 * fact taking ownership. One example of this is the Win32
329 * native compile of emacs.
330 */
331
332 /* Bail when we still own the clipboard */
333 if (hwnd == GetClipboardOwner()) {
334
335 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
336 "We own the clipboard, returning.\n");
337 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
338 s_fProcessingDrawClipboard = FALSE;
339 if (s_hwndNextViewer)
340 SendMessage(s_hwndNextViewer, message, wParam, lParam);
341 return 0;
342 }
343
344 /* Bail when shutting down */
345 if (!fRunning)
346 return 0;
347
348 /*
349 * Do not take ownership of the X11 selections when something
350 * other than CF_TEXT or CF_UNICODETEXT has been copied
351 * into the Win32 clipboard.
352 */
353 if (!IsClipboardFormatAvailable(CF_TEXT)
354 && !IsClipboardFormatAvailable(CF_UNICODETEXT)) {
355
356 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
357 "Clipboard does not contain CF_TEXT nor "
358 "CF_UNICODETEXT.\n");
359
360 /*
361 * We need to make sure that the X Server has processed
362 * previous XSetSelectionOwner messages.
363 */
364 XSync(pDisplay, FALSE);
365
366 winDebug("winClipboardWindowProc - XSync done.\n");
367
368 /* Release PRIMARY selection if owned */
369 iReturn = XGetSelectionOwner(pDisplay, XA_PRIMARY);
370 if (iReturn == iWindow) {
371 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
372 "PRIMARY selection is owned by us.\n");
373 XSetSelectionOwner(pDisplay, XA_PRIMARY, None, CurrentTime);
374 }
375 else if (BadWindow == iReturn || BadAtom == iReturn)
376 ErrorF("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
377 "XGetSelectionOwner failed for PRIMARY: %d\n",
378 iReturn);
379
380 /* Release CLIPBOARD selection if owned */
381 iReturn = XGetSelectionOwner(pDisplay, atoms->atomClipboard);
382 if (iReturn == iWindow) {
383 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
384 "CLIPBOARD selection is owned by us, releasing\n");
385 XSetSelectionOwner(pDisplay, atoms->atomClipboard, None, CurrentTime);
386 }
387 else if (BadWindow == iReturn || BadAtom == iReturn)
388 ErrorF("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
389 "XGetSelectionOwner failed for CLIPBOARD: %d\n",
390 iReturn);
391
392 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
393 s_fProcessingDrawClipboard = FALSE;
394 if (s_hwndNextViewer)
395 SendMessage(s_hwndNextViewer, message, wParam, lParam);
396 return 0;
397 }
398
399 /* Reassert ownership of PRIMARY */
400 iReturn = XSetSelectionOwner(pDisplay,
401 XA_PRIMARY, iWindow, CurrentTime);
402 if (iReturn == BadAtom || iReturn == BadWindow ||
403 XGetSelectionOwner(pDisplay, XA_PRIMARY) != iWindow) {
404 ErrorF("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
405 "Could not reassert ownership of PRIMARY\n");
406 }
407 else {
408 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
409 "Reasserted ownership of PRIMARY\n");
410 }
411
412 /* Reassert ownership of the CLIPBOARD */
413 iReturn = XSetSelectionOwner(pDisplay,
414 atoms->atomClipboard, iWindow, CurrentTime);
415
416 if (iReturn == BadAtom || iReturn == BadWindow ||
417 XGetSelectionOwner(pDisplay, atoms->atomClipboard) != iWindow) {
418 ErrorF("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
419 "Could not reassert ownership of CLIPBOARD\n");
420 }
421 else {
422 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
423 "Reasserted ownership of CLIPBOARD\n");
424 }
425
426 /* Flush the pending SetSelectionOwner event now */
427 XFlush(pDisplay);
428
429 s_fProcessingDrawClipboard = FALSE;
430 }
431 winDebug("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
432 /* Pass the message on the next window in the clipboard viewer chain */
433 if (s_hwndNextViewer)
434 SendMessage(s_hwndNextViewer, message, wParam, lParam);
435 return 0;
436
437 case WM_DESTROYCLIPBOARD:
438 /*
439 * NOTE: Intentionally do nothing.
440 * Changes in the Win32 clipboard are handled by WM_DRAWCLIPBOARD
441 * above. We only process this message to conform to the specs
442 * for delayed clipboard rendering in Win32. You might think
443 * that we need to release ownership of the X11 selections, but
444 * we do not, because a WM_DRAWCLIPBOARD message will closely
445 * follow this message and reassert ownership of the X11
446 * selections, handling the issue for us.
447 */
448 winDebug("winClipboardWindowProc - WM_DESTROYCLIPBOARD - Ignored.\n");
449 return 0;
450
451 case WM_RENDERALLFORMATS:
452 winDebug("winClipboardWindowProc - WM_RENDERALLFORMATS - Hello.\n");
453
454 /*
455 WM_RENDERALLFORMATS is sent as we are shutting down, to render the
456 clipboard so it's contents remains available to other applications.
457
458 Unfortunately, this can't work without major changes. The server is
459 already waiting for us to stop, so we can't ask for the rendering of
460 clipboard text now.
461 */
462
463 return 0;
464
465 case WM_RENDERFORMAT:
466 {
467 int iReturn;
468 Bool fConvertToUnicode;
469 Bool pasted = FALSE;
470 Atom selection;
471 ClipboardConversionData data;
472 int best_target = 0;
473
474 winDebug("winClipboardWindowProc - WM_RENDERFORMAT %d - Hello.\n",
475 (int)wParam);
476
477 /* Flag whether to convert to Unicode or not */
478 fConvertToUnicode = (CF_UNICODETEXT == wParam);
479
480 selection = winClipboardGetLastOwnedSelectionAtom(atoms);
481 if (selection == None) {
482 ErrorF("winClipboardWindowProc - no monitored selection is owned\n");
483 goto fake_paste;
484 }
485
486 winDebug("winClipboardWindowProc - requesting targets for selection from owner\n");
487
488 /* Request the selection's supported conversion targets */
489 XConvertSelection(pDisplay,
490 selection,
491 atoms->atomTargets,
492 atoms->atomLocalProperty,
493 iWindow, CurrentTime);
494
495 /* Process X events */
496 data.fUseUnicode = fConvertToUnicode;
497 iReturn = winProcessXEventsTimeout(hwnd,
498 iWindow,
499 pDisplay,
500 &data,
501 atoms,
502 WIN_POLL_TIMEOUT);
503
504 if (WIN_XEVENTS_NOTIFY_TARGETS != iReturn) {
505 ErrorF
506 ("winClipboardWindowProc - timed out waiting for WIN_XEVENTS_NOTIFY_TARGETS\n");
507 goto fake_paste;
508 }
509
510 /* Choose the most preferred target */
511 {
512 struct target_priority
513 {
514 Atom target;
515 unsigned int priority;
516 };
517
518 struct target_priority target_priority_table[] =
519 {
520 { atoms->atomCompoundText, 0 },
521 #ifdef X_HAVE_UTF8_STRING
522 { atoms->atomUTF8String, 1 },
523 #endif
524 { XA_STRING, 2 },
525 };
526
527 int best_priority = INT_MAX;
528
529 int i,j;
530 for (i = 0 ; data.targetList[i] != 0; i++)
531 {
532 for (j = 0; j < ARRAY_SIZE(target_priority_table); j ++)
533 {
534 if ((data.targetList[i] == target_priority_table[j].target) &&
535 (target_priority_table[j].priority < best_priority))
536 {
537 best_target = target_priority_table[j].target;
538 best_priority = target_priority_table[j].priority;
539 }
540 }
541 }
542 }
543
544 free(data.targetList);
545 data.targetList = 0;
546
547 winDebug("winClipboardWindowProc - best target is %d\n", best_target);
548
549 /* No useful targets found */
550 if (best_target == 0)
551 goto fake_paste;
552
553 winDebug("winClipboardWindowProc - requesting selection from owner\n");
554
555 /* Request the selection contents */
556 XConvertSelection(pDisplay,
557 selection,
558 best_target,
559 atoms->atomLocalProperty,
560 iWindow, CurrentTime);
561
562 /* Process X events */
563 iReturn = winProcessXEventsTimeout(hwnd,
564 iWindow,
565 pDisplay,
566 &data,
567 atoms,
568 WIN_POLL_TIMEOUT);
569
570 /*
571 * winProcessXEventsTimeout had better have seen a notify event,
572 * or else we are dealing with a buggy or old X11 app.
573 */
574 if (WIN_XEVENTS_NOTIFY_DATA != iReturn) {
575 ErrorF
576 ("winClipboardWindowProc - timed out waiting for WIN_XEVENTS_NOTIFY_DATA\n");
577 }
578 else {
579 pasted = TRUE;
580 }
581
582 /*
583 * If we couldn't get the data from the X clipboard, we
584 * have to paste some fake data to the Win32 clipboard to
585 * satisfy the requirement that we write something to it.
586 */
587 fake_paste:
588 if (!pasted)
589 {
590 /* Paste no data, to satisfy required call to SetClipboardData */
591 SetClipboardData(CF_UNICODETEXT, NULL);
592 SetClipboardData(CF_TEXT, NULL);
593 }
594
595 winDebug("winClipboardWindowProc - WM_RENDERFORMAT - Returning.\n");
596 return 0;
597 }
598 }
599
600 /* Let Windows perform default processing for unhandled messages */
601 return DefWindowProc(hwnd, message, wParam, lParam);
602 }
603
604 /*
605 * Process any pending Windows messages
606 */
607
608 Bool
winClipboardFlushWindowsMessageQueue(HWND hwnd)609 winClipboardFlushWindowsMessageQueue(HWND hwnd)
610 {
611 MSG msg;
612
613 /* Flush the messaging window queue */
614 /* NOTE: Do not pass the hwnd of our messaging window to PeekMessage,
615 * as this will filter out many non-window-specific messages that
616 * are sent to our thread, such as WM_QUIT.
617 */
618 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
619 /* Dispatch the message if not WM_QUIT */
620 if (msg.message == WM_QUIT)
621 return FALSE;
622 else
623 DispatchMessage(&msg);
624 }
625
626 return TRUE;
627 }
628