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#define WINVER 0x0600
34
35#ifdef HAVE_XWIN_CONFIG_H
36#include <xwin-config.h>
37#endif
38
39#include <sys/types.h>
40#include <sys/time.h>
41#include <limits.h>
42
43#include <xcb/xproto.h>
44#include <xcb/xcb_aux.h>
45
46#include "internal.h"
47#include "winclipboard.h"
48
49/*
50 * Constants
51 */
52
53#define WIN_POLL_TIMEOUT	1
54
55/*
56 * Process X events up to specified timeout
57 */
58
59static int
60winProcessXEventsTimeout(HWND hwnd, xcb_window_t iWindow, xcb_connection_t *conn,
61                         ClipboardConversionData *data, ClipboardAtoms *atoms, int iTimeoutSec)
62{
63    int iConnNumber;
64    struct timeval tv;
65    int iReturn;
66
67    winDebug("winProcessXEventsTimeout () - pumping X events, timeout %d seconds\n",
68             iTimeoutSec);
69
70    /* Get our connection number */
71    iConnNumber = xcb_get_file_descriptor(conn);
72
73    /* Loop for X events */
74    while (1) {
75        fd_set fdsRead;
76        long remainingTime;
77
78        /* Process X events */
79        iReturn = winClipboardFlushXEvents(hwnd, iWindow, conn, data, atoms);
80
81        winDebug("winProcessXEventsTimeout () - winClipboardFlushXEvents returned %d\n", iReturn);
82
83        if ((WIN_XEVENTS_NOTIFY_DATA == iReturn) || (WIN_XEVENTS_NOTIFY_TARGETS == iReturn) || (WIN_XEVENTS_FAILED == iReturn)) {
84          /* Bail out */
85          return iReturn;
86        }
87
88        /* We need to ensure that all pending requests are sent */
89        xcb_flush(conn);
90
91        /* Setup the file descriptor set */
92        FD_ZERO(&fdsRead);
93        FD_SET(iConnNumber, &fdsRead);
94
95        /* Adjust timeout */
96        remainingTime = iTimeoutSec * 1000;
97        tv.tv_sec = remainingTime / 1000;
98        tv.tv_usec = (remainingTime % 1000) * 1000;
99
100        /* Break out if no time left */
101        if (remainingTime <= 0)
102            return WIN_XEVENTS_SUCCESS;
103
104        /* Wait for an X event */
105        iReturn = select(iConnNumber + 1,       /* Highest fds number */
106                         &fdsRead,      /* Read mask */
107                         NULL,  /* No write mask */
108                         NULL,  /* No exception mask */
109                         &tv);  /* Timeout */
110        if (iReturn < 0) {
111            ErrorF("winProcessXEventsTimeout - Call to select () failed: %d.  "
112                   "Bailing.\n", iReturn);
113            break;
114        }
115
116        if (!FD_ISSET(iConnNumber, &fdsRead)) {
117            winDebug("winProcessXEventsTimeout - Spurious wake, select() returned %d\n", iReturn);
118        }
119    }
120
121    return WIN_XEVENTS_SUCCESS;
122}
123
124/*
125 * Process a given Windows message
126 */
127
128LRESULT CALLBACK
129winClipboardWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
130{
131    static xcb_connection_t *conn;
132    static xcb_window_t iWindow;
133    static ClipboardAtoms *atoms;
134    static BOOL fRunning;
135
136    /* Branch on message type */
137    switch (message) {
138    case WM_DESTROY:
139    {
140        winDebug("winClipboardWindowProc - WM_DESTROY\n");
141
142        /* Remove clipboard listener */
143        RemoveClipboardFormatListener(hwnd);
144    }
145        return 0;
146
147    case WM_WM_QUIT:
148    {
149        winDebug("winClipboardWindowProc - WM_WM_QUIT\n");
150        fRunning = FALSE;
151        PostQuitMessage(0);
152    }
153        return 0;
154
155    case WM_CREATE:
156    {
157        ClipboardWindowCreationParams *cwcp = (ClipboardWindowCreationParams *)((CREATESTRUCT *)lParam)->lpCreateParams;
158
159        winDebug("winClipboardWindowProc - WM_CREATE\n");
160
161        conn = cwcp->pClipboardDisplay;
162        iWindow = cwcp->iClipboardWindow;
163        atoms = cwcp->atoms;
164        fRunning = TRUE;
165
166        AddClipboardFormatListener(hwnd);
167    }
168        return 0;
169
170    case WM_CLIPBOARDUPDATE:
171    {
172        xcb_generic_error_t *error;
173        xcb_void_cookie_t cookie_set;
174
175        winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE: Enter\n");
176
177        /*
178         * NOTE: We cannot bail out when NULL == GetClipboardOwner ()
179         * because some applications deal with the clipboard in a manner
180         * that causes the clipboard owner to be NULL when they are in
181         * fact taking ownership.  One example of this is the Win32
182         * native compile of emacs.
183         */
184
185        /* Bail when we still own the clipboard */
186        if (hwnd == GetClipboardOwner()) {
187
188            winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE - "
189                     "We own the clipboard, returning.\n");
190            winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE: Exit\n");
191
192            return 0;
193        }
194
195        /* Bail when shutting down */
196        if (!fRunning)
197            return 0;
198
199        /*
200         * Do not take ownership of the X11 selections when something
201         * other than CF_TEXT or CF_UNICODETEXT has been copied
202         * into the Win32 clipboard.
203         */
204        if (!IsClipboardFormatAvailable(CF_TEXT)
205            && !IsClipboardFormatAvailable(CF_UNICODETEXT)) {
206
207            xcb_get_selection_owner_cookie_t cookie_get;
208            xcb_get_selection_owner_reply_t *reply;
209
210            winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE - "
211                     "Clipboard does not contain CF_TEXT nor "
212                     "CF_UNICODETEXT.\n");
213
214            /*
215             * We need to make sure that the X Server has processed
216             * previous XSetSelectionOwner messages.
217             */
218            xcb_aux_sync(conn);
219
220            winDebug("winClipboardWindowProc - XSync done.\n");
221
222            /* Release PRIMARY selection if owned */
223            cookie_get = xcb_get_selection_owner(conn, XCB_ATOM_PRIMARY);
224            reply = xcb_get_selection_owner_reply(conn, cookie_get, NULL);
225            if (reply) {
226                if (reply->owner == iWindow) {
227                    winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE - "
228                             "PRIMARY selection is owned by us, releasing.\n");
229                    xcb_set_selection_owner(conn, XCB_NONE, XCB_ATOM_PRIMARY, XCB_CURRENT_TIME);
230                }
231                free(reply);
232            }
233
234            /* Release CLIPBOARD selection if owned */
235            cookie_get = xcb_get_selection_owner(conn, atoms->atomClipboard);
236            reply = xcb_get_selection_owner_reply(conn, cookie_get, NULL);
237            if (reply) {
238                if (reply->owner == iWindow) {
239                    winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE - "
240                             "CLIPBOARD selection is owned by us, releasing\n");
241                    xcb_set_selection_owner(conn, XCB_NONE, atoms->atomClipboard, XCB_CURRENT_TIME);
242                }
243                free(reply);
244            }
245
246            winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE: Exit\n");
247
248            return 0;
249        }
250
251        /* Reassert ownership of PRIMARY */
252        cookie_set = xcb_set_selection_owner_checked(conn, iWindow, XCB_ATOM_PRIMARY, XCB_CURRENT_TIME);
253        error = xcb_request_check(conn, cookie_set);
254        if (error) {
255            ErrorF("winClipboardWindowProc - WM_CLIPBOARDUPDATE - "
256                   "Could not reassert ownership of PRIMARY\n");
257            free(error);
258        } else {
259            winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE - "
260                     "Reasserted ownership of PRIMARY\n");
261        }
262
263        /* Reassert ownership of the CLIPBOARD */
264        cookie_set = xcb_set_selection_owner_checked(conn, iWindow, atoms->atomClipboard, XCB_CURRENT_TIME);
265        error = xcb_request_check(conn, cookie_set);
266        if (error) {
267            ErrorF("winClipboardWindowProc - WM_CLIPBOARDUPDATE - "
268                    "Could not reassert ownership of CLIPBOARD\n");
269            free(error);
270        }
271        else {
272            winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE - "
273                     "Reasserted ownership of CLIPBOARD\n");
274        }
275
276        /* Flush the pending SetSelectionOwner event now */
277        xcb_flush(conn);
278    }
279        winDebug("winClipboardWindowProc - WM_CLIPBOARDUPDATE: Exit\n");
280        return 0;
281
282    case WM_DESTROYCLIPBOARD:
283        /*
284         * NOTE: Intentionally do nothing.
285         * Changes in the Win32 clipboard are handled by WM_CLIPBOARDUPDATE
286         * above.  We only process this message to conform to the specs
287         * for delayed clipboard rendering in Win32.  You might think
288         * that we need to release ownership of the X11 selections, but
289         * we do not, because a WM_CLIPBOARDUPDATE message will closely
290         * follow this message and reassert ownership of the X11
291         * selections, handling the issue for us.
292         */
293        winDebug("winClipboardWindowProc - WM_DESTROYCLIPBOARD - Ignored.\n");
294        return 0;
295
296    case WM_RENDERALLFORMATS:
297        winDebug("winClipboardWindowProc - WM_RENDERALLFORMATS - Hello.\n");
298
299        /*
300          WM_RENDERALLFORMATS is sent as we are shutting down, to render the
301          clipboard so its contents remains available to other applications.
302
303          Unfortunately, this can't work without major changes. The server is
304          already waiting for us to stop, so we can't ask for the rendering of
305          clipboard text now.
306        */
307
308        return 0;
309
310    case WM_RENDERFORMAT:
311    {
312        int iReturn;
313        BOOL pasted = FALSE;
314        xcb_atom_t selection;
315        ClipboardConversionData data;
316        int best_target = 0;
317
318        winDebug("winClipboardWindowProc - WM_RENDERFORMAT %d - Hello.\n",
319                 (int)wParam);
320
321        selection = winClipboardGetLastOwnedSelectionAtom(atoms);
322        if (selection == XCB_NONE) {
323            ErrorF("winClipboardWindowProc - no monitored selection is owned\n");
324            goto fake_paste;
325        }
326
327        winDebug("winClipboardWindowProc - requesting targets for selection from owner\n");
328
329        /* Request the selection's supported conversion targets */
330        xcb_convert_selection(conn, iWindow, selection, atoms->atomTargets,
331                              atoms->atomLocalProperty, XCB_CURRENT_TIME);
332
333        /* Process X events */
334        data.incr = NULL;
335        data.incrsize = 0;
336
337        iReturn = winProcessXEventsTimeout(hwnd,
338                                           iWindow,
339                                           conn,
340                                           &data,
341                                           atoms,
342                                           WIN_POLL_TIMEOUT);
343
344        if (WIN_XEVENTS_NOTIFY_TARGETS != iReturn) {
345            ErrorF
346                ("winClipboardWindowProc - timed out waiting for WIN_XEVENTS_NOTIFY_TARGETS\n");
347            goto fake_paste;
348        }
349
350        /* Choose the most preferred target */
351        {
352            struct target_priority
353            {
354                xcb_atom_t target;
355                unsigned int priority;
356            };
357
358            struct target_priority target_priority_table[] =
359                {
360                    { atoms->atomUTF8String,   0 },
361                    // { atoms->atomCompoundText, 1 }, not implemented (yet?)
362                    { XCB_ATOM_STRING,         2 },
363                };
364
365            int best_priority = INT_MAX;
366
367            int i,j;
368            for (i = 0 ; data.targetList[i] != 0; i++)
369                {
370                    for (j = 0; j < ARRAY_SIZE(target_priority_table); j ++)
371                        {
372                            if ((data.targetList[i] == target_priority_table[j].target) &&
373                                (target_priority_table[j].priority < best_priority))
374                                {
375                                    best_target = target_priority_table[j].target;
376                                    best_priority = target_priority_table[j].priority;
377                                }
378                        }
379                }
380        }
381
382        free(data.targetList);
383        data.targetList = 0;
384
385        winDebug("winClipboardWindowProc - best target is %d\n", best_target);
386
387        /* No useful targets found */
388        if (best_target == 0)
389          goto fake_paste;
390
391        winDebug("winClipboardWindowProc - requesting selection from owner\n");
392
393        /* Request the selection contents */
394        xcb_convert_selection(conn, iWindow, selection, best_target,
395                              atoms->atomLocalProperty, XCB_CURRENT_TIME);
396
397        /* Process X events */
398        iReturn = winProcessXEventsTimeout(hwnd,
399                                           iWindow,
400                                           conn,
401                                           &data,
402                                           atoms,
403                                           WIN_POLL_TIMEOUT);
404
405        /*
406         * winProcessXEventsTimeout had better have seen a notify event,
407         * or else we are dealing with a buggy or old X11 app.
408         */
409        if (WIN_XEVENTS_NOTIFY_DATA != iReturn) {
410            ErrorF
411                ("winClipboardWindowProc - timed out waiting for WIN_XEVENTS_NOTIFY_DATA\n");
412        }
413        else {
414            pasted = TRUE;
415        }
416
417         /*
418          * If we couldn't get the data from the X clipboard, we
419          * have to paste some fake data to the Win32 clipboard to
420          * satisfy the requirement that we write something to it.
421          */
422    fake_paste:
423        if (!pasted)
424          {
425            /* Paste no data, to satisfy required call to SetClipboardData */
426            SetClipboardData(CF_UNICODETEXT, NULL);
427            SetClipboardData(CF_TEXT, NULL);
428          }
429
430        winDebug("winClipboardWindowProc - WM_RENDERFORMAT - Returning.\n");
431        return 0;
432    }
433    }
434
435    /* Let Windows perform default processing for unhandled messages */
436    return DefWindowProc(hwnd, message, wParam, lParam);
437}
438
439/*
440 * Process any pending Windows messages
441 */
442
443BOOL
444winClipboardFlushWindowsMessageQueue(HWND hwnd)
445{
446    MSG msg;
447
448    /* Flush the messaging window queue */
449    /* NOTE: Do not pass the hwnd of our messaging window to PeekMessage,
450     * as this will filter out many non-window-specific messages that
451     * are sent to our thread, such as WM_QUIT.
452     */
453    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
454        /* Dispatch the message if not WM_QUIT */
455        if (msg.message == WM_QUIT)
456            return FALSE;
457        else
458            DispatchMessage(&msg);
459    }
460
461    return TRUE;
462}
463