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_CLIPBOARD_PROP	"cyg_clipboard_prop"
46#define WIN_POLL_TIMEOUT	1
47
48
49/*
50 * References to external symbols
51 */
52
53extern Bool		g_fUseUnicode;
54extern Bool		g_fUnicodeSupport;
55extern void		*g_pClipboardDisplay;
56extern Window		g_iClipboardWindow;
57extern Atom		g_atomLastOwnedSelection;
58
59/* BPS - g_hwndClipboard needed for X app->Windows paste fix */
60extern HWND		g_hwndClipboard;
61
62/*
63 * Local function prototypes
64 */
65
66static int
67winProcessXEventsTimeout (HWND hwnd, int iWindow, Display *pDisplay,
68			  Bool fUseUnicode, int iTimeoutSec);
69
70
71/*
72 * Process X events up to specified timeout
73 */
74
75static int
76winProcessXEventsTimeout (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;
83
84  /* We need to ensure that all pending events are processed */
85  XSync (pDisplay, FALSE);
86
87  /* Get our connection number */
88  iConnNumber = ConnectionNumber (pDisplay);
89
90  /* Loop for X events */
91  while (1)
92    {
93      fd_set		fdsRead;
94
95      /* Setup the file descriptor set */
96      FD_ZERO (&fdsRead);
97      FD_SET (iConnNumber, &fdsRead);
98
99      /* Adjust timeout */
100      tv.tv_sec = dwStopTime - (GetTickCount () / 1000);
101      tv.tv_usec = 0;
102
103      /* Break out if no time left */
104      if (tv.tv_sec < 0)
105	return WIN_XEVENTS_SUCCESS;
106
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	}
119
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    }
137
138  return WIN_XEVENTS_SUCCESS;
139}
140
141
142/*
143 * Process a given Windows message
144 */
145
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)
151
152LRESULT CALLBACK
153winClipboardWindowProc (HWND hwnd, UINT message,
154			WPARAM wParam, LPARAM lParam)
155{
156  static HWND		s_hwndNextViewer;
157  static Bool		s_fCBCInitialized;
158
159  /* Branch on message type */
160  switch (message)
161    {
162    case WM_DESTROY:
163      {
164	winDebug ("winClipboardWindowProc - WM_DESTROY\n");
165
166	/* Remove ourselves from the clipboard chain */
167	ChangeClipboardChain (hwnd, s_hwndNextViewer);
168
169	s_hwndNextViewer = NULL;
170
171	PostQuitMessage (0);
172      }
173      return 0;
174
175
176    case WM_CREATE:
177      {
178	HWND first, next;
179	DWORD error_code = 0;
180	winDebug ("winClipboardWindowProc - WM_CREATE\n");
181
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;
193
194
195    case WM_CHANGECBCHAIN:
196      {
197	winDebug ("winClipboardWindowProc - WM_CHANGECBCHAIN: wParam(%x) "
198		  "lParam(%x) s_hwndNextViewer(%x)\n",
199		  wParam, lParam, s_hwndNextViewer);
200
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);
214
215      }
216      winDebug ("winClipboardWindowProc - WM_CHANGECBCHAIN: Exit\n");
217      return 0;
218
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	 */
233
234	HWND first, next;
235	DWORD error_code = 0;
236	winDebug ("winClipboardWindowProc - WM_WM_REINIT: Enter\n");
237
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;
258
259
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;
268
269	winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD: Enter\n");
270
271	if (generation != serverGeneration)
272          {
273            generation = serverGeneration;
274            atomClipboard = XInternAtom (pDisplay, "CLIPBOARD", False);
275          }
276
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	  }
297
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	  }
306
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	 */
314
315	/* Bail when we still own the clipboard */
316	if (hwnd == GetClipboardOwner ())
317	  {
318
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	  }
327
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	  {
336
337	    winDebug ("winClipboardWindowProc - WM_DRAWCLIPBOARD - "
338		    "Clipboard does not contain CF_TEXT nor "
339		    "CF_UNICODETEXT.\n");
340
341	    /*
342	     * We need to make sure that the X Server has processed
343	     * previous XSetSelectionOwner messages.
344	     */
345	    XSync (pDisplay, FALSE);
346
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);
361
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);
377
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	  }
384
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	  }
401
402	/* Reassert ownership of the CLIPBOARD */
403	iReturn = XSetSelectionOwner (pDisplay,
404				      atomClipboard,
405				      iWindow,
406				      CurrentTime);
407
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	  }
419
420	/* Flush the pending SetSelectionOwner event now */
421	XFlush (pDisplay);
422
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;
430
431
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;
445
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;
453
454	winDebug ("winClipboardWindowProc - WM_RENDER*FORMAT - Hello.\n");
455
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);
461
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	  }
477
478	/* Special handling for WM_RENDERALLFORMATS */
479	if (message == WM_RENDERALLFORMATS)
480	  {
481	    /* We must open and empty the clipboard */
482
483	    /* Close clipboard if we have it open already */
484	    if (GetOpenClipboardWindow () == hwnd)
485	      {
486		CloseClipboard ();
487	      }
488
489	    if (!OpenClipboard (hwnd))
490	      {
491		winErrorFVerb (1, "winClipboardWindowProc - WM_RENDER*FORMATS - "
492			"OpenClipboard () failed: %08x\n",
493			GetLastError ());
494		break;
495	      }
496
497	    if (!EmptyClipboard ())
498	      {
499		winErrorFVerb (1, "winClipboardWindowProc - WM_RENDER*FORMATS - "
500			"EmptyClipboard () failed: %08x\n",
501		      GetLastError ());
502		break;
503	      }
504	  }
505
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	  }
525
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);
539
540            ErrorF("winClipboardWindowProc - timed out waiting for WIN_XEVENTS_NOTIFY\n");
541	  }
542
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);
549
550	/* Special handling for WM_RENDERALLFORMATS */
551	if (message == WM_RENDERALLFORMATS)
552	  {
553	    /* We must close the clipboard */
554
555	    if (!CloseClipboard ())
556	      {
557	      winErrorFVerb (1, "winClipboardWindowProc - WM_RENDERALLFORMATS - "
558		      "CloseClipboard () failed: %08x\n",
559		      GetLastError ());
560	      break;
561	      }
562	  }
563
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");
580
581	/* Set up for another delayed rendering callback */
582	OpenClipboard (g_hwndClipboard);
583
584	/* Take ownership of the Windows clipboard */
585	EmptyClipboard ();
586
587	/* Advertise Unicode if we support it */
588	if (g_fUnicodeSupport)
589	  SetClipboardData (CF_UNICODETEXT, NULL);
590
591	/* Always advertise regular text */
592	SetClipboardData (CF_TEXT, NULL);
593
594	/* Release the clipboard */
595	CloseClipboard ();
596      }
597      return 0;
598    }
599
600  /* Let Windows perform default processing for unhandled messages */
601  return DefWindowProc (hwnd, message, wParam, lParam);
602}
603
604
605/*
606 * Process any pending Windows messages
607 */
608
609BOOL
610winClipboardFlushWindowsMessageQueue (HWND hwnd)
611{
612  MSG			msg;
613
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    }
627
628  return TRUE;
629}
630