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 */
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"
41 /*
42 * Constants
43 */
45 #define WIN_CLIPBOARD_PROP "cyg_clipboard_prop"
46 #define WIN_POLL_TIMEOUT 1
49 /*
50 * References to external symbols
51 */
53 extern Bool g_fUseUnicode;
54 extern Bool g_fUnicodeSupport;
55 extern void *g_pClipboardDisplay;
56 extern Window g_iClipboardWindow;
57 extern Atom g_atomLastOwnedSelection;
59 /* BPS - g_hwndClipboard needed for X app->Windows paste fix */
60 extern HWND g_hwndClipboard;
62 /*
63 * Local function prototypes
64 */
66 static int
67 winProcessXEventsTimeout (HWND hwnd, int iWindow, Display *pDisplay,
68 Bool fUseUnicode, int iTimeoutSec);
71 /*
72 * Process X events up to specified timeout
73 */
75 static int
76 winProcessXEventsTimeout (HWND hwnd, int iWindow, Display *pDisplay,
77 Bool fUseUnicode, int iTimeoutSec)
78 {
79 int iConnNumber;
80 struct timeval tv;
81 int iReturn;
82 DWORD dwStopTime = (GetTickCount () / 1000) + iTimeoutSec;
84 /* We need to ensure that all pending events are processed */
85 XSync (pDisplay, FALSE);
87 /* Get our connection number */
88 iConnNumber = ConnectionNumber (pDisplay);
90 /* Loop for X events */
91 while (1)
92 {
93 fd_set fdsRead;
95 /* Setup the file descriptor set */
96 FD_ZERO (&fdsRead);
97 FD_SET (iConnNumber, &fdsRead);
99 /* Adjust timeout */
100 tv.tv_sec = dwStopTime - (GetTickCount () / 1000);
101 tv.tv_usec = 0;
103 /* Break out if no time left */
104 if (tv.tv_sec < 0)
105 return WIN_XEVENTS_SUCCESS;
107 /* Wait for an X event */
108 iReturn = select (iConnNumber + 1,/* Highest fds number */
109 &fdsRead, /* Read mask */
110 NULL, /* No write mask */
111 NULL, /* No exception mask */
112 &tv); /* No timeout */
113 if (iReturn < 0)
114 {
115 ErrorF ("winProcessXEventsTimeout - Call to select () failed: %d. "
116 "Bailing.\n", iReturn);
117 break;
118 }
120 /* Branch on which descriptor became active */
121 if (FD_ISSET (iConnNumber, &fdsRead))
122 {
123 /* Process X events */
124 /* Exit when we see that server is shutting down */
125 iReturn = winClipboardFlushXEvents (hwnd,
126 iWindow,
127 pDisplay,
128 fUseUnicode);
129 if (WIN_XEVENTS_NOTIFY == iReturn
130 || WIN_XEVENTS_CONVERT == iReturn)
131 {
132 /* Bail out if convert or notify processed */
133 return iReturn;
134 }
135 }
136 }
138 return WIN_XEVENTS_SUCCESS;
139 }
142 /*
143 * Process a given Windows message
144 */
146 /* BPS - Define our own message, which we'll post to ourselves to facilitate
147 * resetting the delayed rendering mechanism after each paste from X app to
148 * Windows app. TODO - Perhaps move to win.h with the other WM_USER messages.
149 */
150 #define WM_USER_PASTE_COMPLETE (WM_USER + 1003)
152 LRESULT CALLBACK
153 winClipboardWindowProc (HWND hwnd, UINT message,
154 WPARAM wParam, LPARAM lParam)
155 {
156 static HWND s_hwndNextViewer;
157 static Bool s_fCBCInitialized;
159 /* Branch on message type */
160 switch (message)
161 {
162 case WM_DESTROY:
163 {
164 winDebug ("winClipboardWindowProc - WM_DESTROY\n");
166 /* Remove ourselves from the clipboard chain */
167 ChangeClipboardChain (hwnd, s_hwndNextViewer);
169 s_hwndNextViewer = NULL;
171 PostQuitMessage (0);
172 }
173 return 0;
176 case WM_CREATE:
177 {
178 HWND first, next;
179 DWORD error_code = 0;
180 winDebug ("winClipboardWindowProc - WM_CREATE\n");
182 first = GetClipboardViewer(); /* Get handle to first viewer in chain. */
183 if (first == hwnd) return 0; /* Make sure it's not us! */
184 /* Add ourselves to the clipboard viewer chain */
185 next = SetClipboardViewer (hwnd);
186 error_code = GetLastError();
187 if (SUCCEEDED(error_code) && (next == first)) /* SetClipboardViewer must have succeeded, and the handle */
188 s_hwndNextViewer = next; /* it returned must have been the first window in the chain */
189 else
190 s_fCBCInitialized = FALSE;
191 }
192 return 0;
195 case WM_CHANGECBCHAIN:
196 {
197 winDebug ("winClipboardWindowProc - WM_CHANGECBCHAIN: wParam(%x) "
198 "lParam(%x) s_hwndNextViewer(%x)\n",
199 wParam, lParam, s_hwndNextViewer);
201 if ((HWND) wParam == s_hwndNextViewer)
202 {
203 s_hwndNextViewer = (HWND) lParam;
204 if (s_hwndNextViewer == hwnd)
205 {
206 s_hwndNextViewer = NULL;
207 winErrorFVerb (1, "winClipboardWindowProc - WM_CHANGECBCHAIN: "
208 "attempted to set next window to ourselves.");
209 }
210 }
211 else if (s_hwndNextViewer)
212 SendMessage (s_hwndNextViewer, message,
213 wParam, lParam);
215 }
216 winDebug ("winClipboardWindowProc - WM_CHANGECBCHAIN: Exit\n");
217 return 0;
219 case WM_WM_REINIT:
220 {
221 /* Ensure that we're in the clipboard chain. Some apps,
222 * WinXP's remote desktop for one, don't play nice with the
223 * chain. This message is called whenever we receive a
224 * WM_ACTIVATEAPP message to ensure that we continue to
225 * receive clipboard messages.
226 *
227 * It might be possible to detect if we're still in the chain
228 * by calling SendMessage (GetClipboardViewer(),
229 * WM_DRAWCLIPBOARD, 0, 0); and then seeing if we get the
230 * WM_DRAWCLIPBOARD message. That, however, might be more
231 * expensive than just putting ourselves back into the chain.
232 */
234 HWND first, next;
235 DWORD error_code = 0;
236 winDebug ("winClipboardWindowProc - WM_WM_REINIT: Enter\n");
238 first = GetClipboardViewer(); /* Get handle to first viewer in chain. */
239 if (first == hwnd) return 0; /* Make sure it's not us! */
240 winDebug (" WM_WM_REINIT: Replacing us(%x) with %x at head "
241 "of chain\n", hwnd, s_hwndNextViewer);
242 s_fCBCInitialized = FALSE;
243 ChangeClipboardChain (hwnd, s_hwndNextViewer);
244 s_hwndNextViewer = NULL;
245 s_fCBCInitialized = FALSE;
246 winDebug (" WM_WM_REINIT: Putting us back at head of chain.\n");
247 first = GetClipboardViewer(); /* Get handle to first viewer in chain. */
248 if (first == hwnd) return 0; /* Make sure it's not us! */
249 next = SetClipboardViewer (hwnd);
250 error_code = GetLastError();
251 if (SUCCEEDED(error_code) && (next == first)) /* SetClipboardViewer must have succeeded, and the handle */
252 s_hwndNextViewer = next; /* it returned must have been the first window in the chain */
253 else
254 s_fCBCInitialized = FALSE;
255 }
256 winDebug ("winClipboardWindowProc - WM_WM_REINIT: Exit\n");
257 return 0;
260 case WM_DRAWCLIPBOARD:
261 {
262 static Atom atomClipboard;
263 static int generation;
264 static Bool s_fProcessingDrawClipboard = FALSE;
265 Display *pDisplay = g_pClipboardDisplay;
266 Window iWindow = g_iClipboardWindow;
267 int iReturn;
269 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD: Enter\n");
271 if (generation != serverGeneration)
272 {
273 generation = serverGeneration;
274 atomClipboard = XInternAtom (pDisplay, "CLIPBOARD", False);
275 }
277 /*
278 * We've occasionally seen a loop in the clipboard chain.
279 * Try and fix it on the first hint of recursion.
280 */
281 if (! s_fProcessingDrawClipboard)
282 {
283 s_fProcessingDrawClipboard = TRUE;
284 }
285 else
286 {
287 /* Attempt to break the nesting by getting out of the chain, twice?, and then fix and bail */
288 s_fCBCInitialized = FALSE;
289 ChangeClipboardChain (hwnd, s_hwndNextViewer);
290 winFixClipboardChain();
291 winErrorFVerb (1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - "
292 "Nested calls detected. Re-initing.\n");
293 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
294 s_fProcessingDrawClipboard = FALSE;
295 return 0;
296 }
298 /* Bail on first message */
299 if (!s_fCBCInitialized)
300 {
301 s_fCBCInitialized = TRUE;
302 s_fProcessingDrawClipboard = FALSE;
303 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
304 return 0;
305 }
307 /*
308 * NOTE: We cannot bail out when NULL == GetClipboardOwner ()
309 * because some applications deal with the clipboard in a manner
310 * that causes the clipboard owner to be NULL when they are in
311 * fact taking ownership. One example of this is the Win32
312 * native compile of emacs.
313 */
315 /* Bail when we still own the clipboard */
316 if (hwnd == GetClipboardOwner ())
317 {
319 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
320 "We own the clipboard, returning.\n");
321 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
322 s_fProcessingDrawClipboard = FALSE;
323 if (s_hwndNextViewer)
324 SendMessage (s_hwndNextViewer, message, wParam, lParam);
325 return 0;
326 }
328 /*
329 * Do not take ownership of the X11 selections when something
330 * other than CF_TEXT or CF_UNICODETEXT has been copied
331 * into the Win32 clipboard.
332 */
333 if (!IsClipboardFormatAvailable (CF_TEXT)
334 && !IsClipboardFormatAvailable (CF_UNICODETEXT))
335 {
337 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
338 "Clipboard does not contain CF_TEXT nor "
339 "CF_UNICODETEXT.\n");
341 /*
342 * We need to make sure that the X Server has processed
343 * previous XSetSelectionOwner messages.
344 */
345 XSync (pDisplay, FALSE);
347 /* Release PRIMARY selection if owned */
348 iReturn = XGetSelectionOwner (pDisplay, XA_PRIMARY);
349 if (iReturn == g_iClipboardWindow)
350 {
351 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
352 "PRIMARY selection is owned by us.\n");
353 XSetSelectionOwner (pDisplay,
354 XA_PRIMARY,
355 None,
356 CurrentTime);
357 }
358 else if (BadWindow == iReturn || BadAtom == iReturn)
359 winErrorFVerb (1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - "
360 "XGetSelection failed for PRIMARY: %d\n", iReturn);
362 /* Release CLIPBOARD selection if owned */
363 iReturn = XGetSelectionOwner (pDisplay,
364 atomClipboard);
365 if (iReturn == g_iClipboardWindow)
366 {
367 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
368 "CLIPBOARD selection is owned by us.\n");
369 XSetSelectionOwner (pDisplay,
370 atomClipboard,
371 None,
372 CurrentTime);
373 }
374 else if (BadWindow == iReturn || BadAtom == iReturn)
375 winErrorFVerb (1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - "
376 "XGetSelection failed for CLIPBOARD: %d\n", iReturn);
378 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
379 s_fProcessingDrawClipboard = FALSE;
380 if (s_hwndNextViewer)
381 SendMessage (s_hwndNextViewer, message, wParam, lParam);
382 return 0;
383 }
385 /* Reassert ownership of PRIMARY */
386 iReturn = XSetSelectionOwner (pDisplay,
387 XA_PRIMARY,
388 iWindow,
389 CurrentTime);
390 if (iReturn == BadAtom || iReturn == BadWindow ||
391 XGetSelectionOwner (pDisplay, XA_PRIMARY) != iWindow)
392 {
393 winErrorFVerb (1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - "
394 "Could not reassert ownership of PRIMARY\n");
395 }
396 else
397 {
398 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
399 "Reasserted ownership of PRIMARY\n");
400 }
402 /* Reassert ownership of the CLIPBOARD */
403 iReturn = XSetSelectionOwner (pDisplay,
404 atomClipboard,
405 iWindow,
406 CurrentTime);
408 if (iReturn == BadAtom || iReturn == BadWindow ||
409 XGetSelectionOwner (pDisplay, atomClipboard) != iWindow)
410 {
411 winErrorFVerb (1, "winClipboardWindowProc - WM_DRAWCLIPBOARD - "
412 "Could not reassert ownership of CLIPBOARD\n");
413 }
414 else
415 {
416 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
417 "Reasserted ownership of CLIPBOARD\n");
418 }
420 /* Flush the pending SetSelectionOwner event now */
421 XFlush (pDisplay);
423 s_fProcessingDrawClipboard = FALSE;
424 }
425 winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD: Exit\n");
426 /* Pass the message on the next window in the clipboard viewer chain */
427 if (s_hwndNextViewer)
428 SendMessage (s_hwndNextViewer, message, wParam, lParam);
429 return 0;
432 case WM_DESTROYCLIPBOARD:
433 /*
434 * NOTE: Intentionally do nothing.
435 * Changes in the Win32 clipboard are handled by WM_DRAWCLIPBOARD
436 * above. We only process this message to conform to the specs
437 * for delayed clipboard rendering in Win32. You might think
438 * that we need to release ownership of the X11 selections, but
439 * we do not, because a WM_DRAWCLIPBOARD message will closely
440 * follow this message and reassert ownership of the X11
441 * selections, handling the issue for us.
442 */
443 winDebug ("winClipboardWindowProc - WM_DESTROYCLIPBOARD - Ignored.\n");
444 return 0;
446 case WM_RENDERFORMAT:
447 case WM_RENDERALLFORMATS:
448 {
449 int iReturn;
450 Display *pDisplay = g_pClipboardDisplay;
451 Window iWindow = g_iClipboardWindow;
452 Bool fConvertToUnicode;
454 winDebug ("winClipboardWindowProc - WM_RENDER*FORMAT - Hello.\n");
456 /* Flag whether to convert to Unicode or not */
457 if (message == WM_RENDERALLFORMATS)
458 fConvertToUnicode = FALSE;
459 else
460 fConvertToUnicode = g_fUnicodeSupport && (CF_UNICODETEXT == wParam);
462 /* Request the selection contents */
463 iReturn = XConvertSelection (pDisplay,
464 g_atomLastOwnedSelection,
465 XInternAtom (pDisplay,
466 "COMPOUND_TEXT", False),
467 XInternAtom (pDisplay,
468 "CYGX_CUT_BUFFER", False),
469 iWindow,
470 CurrentTime);
471 if (iReturn == BadAtom || iReturn == BadWindow)
472 {
473 winErrorFVerb (1, "winClipboardWindowProc - WM_RENDER*FORMAT - "
474 "XConvertSelection () failed\n");
475 break;
476 }
478 /* Special handling for WM_RENDERALLFORMATS */
479 if (message == WM_RENDERALLFORMATS)
480 {
481 /* We must open and empty the clipboard */
483 /* Close clipboard if we have it open already */
484 if (GetOpenClipboardWindow () == hwnd)
485 {
486 CloseClipboard ();
487 }
489 if (!OpenClipboard (hwnd))
490 {
491 winErrorFVerb (1, "winClipboardWindowProc - WM_RENDER*FORMATS - "
492 "OpenClipboard () failed: %08x\n",
493 GetLastError ());
494 break;
495 }
497 if (!EmptyClipboard ())
498 {
499 winErrorFVerb (1, "winClipboardWindowProc - WM_RENDER*FORMATS - "
500 "EmptyClipboard () failed: %08x\n",
501 GetLastError ());
502 break;
503 }
504 }
506 /* Process the SelectionNotify event */
507 iReturn = winProcessXEventsTimeout (hwnd,
508 iWindow,
509 pDisplay,
510 fConvertToUnicode,
511 WIN_POLL_TIMEOUT);
512 if (WIN_XEVENTS_CONVERT == iReturn)
513 {
514 /*
515 * The selection was offered for conversion first, so we have
516 * to process a second SelectionNotify event to get the actual
517 * data in the selection.
518 */
519 iReturn = winProcessXEventsTimeout (hwnd,
520 iWindow,
521 pDisplay,
522 fConvertToUnicode,
523 WIN_POLL_TIMEOUT);
524 }
526 /*
527 * The last of the up-to two calls to winProcessXEventsTimeout
528 * from above had better have seen a notify event, or else we
529 * are dealing with a buggy or old X11 app. In these cases we
530 * have to paste some fake data to the Win32 clipboard to
531 * satisfy the requirement that we write something to it.
532 */
533 if (WIN_XEVENTS_NOTIFY != iReturn)
534 {
535 /* Paste no data, to satisfy required call to SetClipboardData */
536 if (g_fUnicodeSupport)
537 SetClipboardData (CF_UNICODETEXT, NULL);
538 SetClipboardData (CF_TEXT, NULL);
540 ErrorF("winClipboardWindowProc - timed out waiting for WIN_XEVENTS_NOTIFY\n");
541 }
543 /* BPS - Post ourselves a user message whose handler will reset the
544 * delayed rendering mechanism after the paste is complete. This is
545 * necessary because calling SetClipboardData() with a NULL argument
546 * here will cause the data we just put on the clipboard to be lost!
547 */
548 PostMessage(g_hwndClipboard, WM_USER_PASTE_COMPLETE, 0, 0);
550 /* Special handling for WM_RENDERALLFORMATS */
551 if (message == WM_RENDERALLFORMATS)
552 {
553 /* We must close the clipboard */
555 if (!CloseClipboard ())
556 {
557 winErrorFVerb (1, "winClipboardWindowProc - WM_RENDERALLFORMATS - "
558 "CloseClipboard () failed: %08x\n",
559 GetLastError ());
560 break;
561 }
562 }
564 winDebug ("winClipboardWindowProc - WM_RENDER*FORMAT - Returning.\n");
565 return 0;
566 }
567 /* BPS - This WM_USER message is posted by us. It gives us the opportunity
568 * to reset the delayed rendering mechanism after each and every paste
569 * from an X app to a Windows app. Without such a mechanism, subsequent
570 * changes of selection in the X app owning the selection are not
571 * reflected in pastes into Windows apps, since Windows won't send us the
572 * WM_RENDERFORMAT message unless someone has set changed data (or NULL)
573 * on the clipboard. */
574 case WM_USER_PASTE_COMPLETE:
575 {
576 if (hwnd != GetClipboardOwner ())
577 /* In case we've lost the selection since posting the message */
578 return 0;
579 winDebug ("winClipboardWindowProc - WM_USER_PASTE_COMPLETE\n");
581 /* Set up for another delayed rendering callback */
582 OpenClipboard (g_hwndClipboard);
584 /* Take ownership of the Windows clipboard */
585 EmptyClipboard ();
587 /* Advertise Unicode if we support it */
588 if (g_fUnicodeSupport)
589 SetClipboardData (CF_UNICODETEXT, NULL);
591 /* Always advertise regular text */
592 SetClipboardData (CF_TEXT, NULL);
594 /* Release the clipboard */
595 CloseClipboard ();
596 }
597 return 0;
598 }
600 /* Let Windows perform default processing for unhandled messages */
601 return DefWindowProc (hwnd, message, wParam, lParam);
602 }
605 /*
606 * Process any pending Windows messages
607 */
609 BOOL
610 winClipboardFlushWindowsMessageQueue (HWND hwnd)
611 {
612 MSG msg;
614 /* Flush the messaging window queue */
615 /* NOTE: Do not pass the hwnd of our messaging window to PeekMessage,
616 * as this will filter out many non-window-specific messages that
617 * are sent to our thread, such as WM_QUIT.
618 */
619 while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
620 {
621 /* Dispatch the message if not WM_QUIT */
622 if (msg.message == WM_QUIT)
623 return FALSE;
624 else
625 DispatchMessage (&msg);
626 }
628 return TRUE;
629 }