xref: /OK3568_Linux_fs/external/xserver/hw/xwin/winclipboard/wndproc.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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