thread.c revision 35c4bbdf
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#else
36#define HAS_WINSOCK 1
37#endif
38
39/*
40 * Including any server header might define the macro _XSERVER64 on 64 bit machines.
41 * That macro must _NOT_ be defined for Xlib client code, otherwise bad things happen.
42 * So let's undef that macro if necessary.
43 */
44#ifdef _XSERVER64
45#undef _XSERVER64
46#endif
47
48#include <assert.h>
49#include <unistd.h>
50#include <fcntl.h>
51#include <setjmp.h>
52#include <pthread.h>
53#include <sys/param.h> // for MAX() macro
54
55#ifdef HAS_WINSOCK
56#include <X11/Xwinsock.h>
57#else
58#include <errno.h>
59#endif
60
61#include <X11/Xatom.h>
62#include <X11/extensions/Xfixes.h>
63#include "winclipboard.h"
64#include "internal.h"
65
66#define WIN_CONNECT_RETRIES			40
67#define WIN_CONNECT_DELAY			4
68
69#define WIN_CLIPBOARD_WINDOW_CLASS		"xwinclip"
70#define WIN_CLIPBOARD_WINDOW_TITLE		"xwinclip"
71#ifdef HAS_DEVWINDOWS
72#define WIN_MSG_QUEUE_FNAME "/dev/windows"
73#endif
74
75/*
76 * Global variables
77 */
78
79static HWND g_hwndClipboard = NULL;
80static jmp_buf g_jmpEntry;
81static XIOErrorHandler g_winClipboardOldIOErrorHandler;
82static pthread_t g_winClipboardProcThread;
83
84int xfixes_event_base;
85int xfixes_error_base;
86
87Bool g_fHasModernClipboardApi = FALSE;
88ADDCLIPBOARDFORMATLISTENERPROC g_fpAddClipboardFormatListener;
89REMOVECLIPBOARDFORMATLISTENERPROC g_fpRemoveClipboardFormatListener;
90
91/*
92 * Local function prototypes
93 */
94
95static HWND
96winClipboardCreateMessagingWindow(Display *pDisplay, Window iWindow, ClipboardAtoms *atoms);
97
98static int
99 winClipboardErrorHandler(Display * pDisplay, XErrorEvent * pErr);
100
101static int
102 winClipboardIOErrorHandler(Display * pDisplay);
103
104/*
105 * Create X11 and Win32 messaging windows, and run message processing loop
106 *
107 * returns TRUE if shutdown was signalled to loop, FALSE if some error occurred
108 */
109
110Bool
111winClipboardProc(Bool fUseUnicode, char *szDisplay)
112{
113    ClipboardAtoms atoms;
114    int iReturn;
115    HWND hwnd = NULL;
116    int iConnectionNumber = 0;
117
118#ifdef HAS_DEVWINDOWS
119    int fdMessageQueue = 0;
120#else
121    struct timeval tvTimeout;
122#endif
123    fd_set fdsRead;
124    int iMaxDescriptor;
125    Display *pDisplay = NULL;
126    Window iWindow = None;
127    int iSelectError;
128    Bool fShutdown = FALSE;
129    static Bool fErrorHandlerSet = FALSE;
130    ClipboardConversionData data;
131
132    winDebug("winClipboardProc - Hello\n");
133
134    /* Allow multiple threads to access Xlib */
135    if (XInitThreads() == 0) {
136        ErrorF("winClipboardProc - XInitThreads failed.\n");
137        goto winClipboardProc_Exit;
138    }
139
140    /* See if X supports the current locale */
141    if (XSupportsLocale() == False) {
142        ErrorF("winClipboardProc - Warning: Locale not supported by X.\n");
143    }
144
145    g_fpAddClipboardFormatListener = (ADDCLIPBOARDFORMATLISTENERPROC)GetProcAddress(GetModuleHandle("user32"),"AddClipboardFormatListener");
146    g_fpRemoveClipboardFormatListener = (REMOVECLIPBOARDFORMATLISTENERPROC)GetProcAddress(GetModuleHandle("user32"),"RemoveClipboardFormatListener");
147    g_fHasModernClipboardApi = g_fpAddClipboardFormatListener && g_fpRemoveClipboardFormatListener;
148    ErrorF("OS maintains clipboard viewer chain: %s\n", g_fHasModernClipboardApi ? "yes" : "no");
149
150    g_winClipboardProcThread = pthread_self();
151
152    /* Set error handler */
153    if (!fErrorHandlerSet) {
154      XSetErrorHandler(winClipboardErrorHandler);
155      g_winClipboardOldIOErrorHandler =
156         XSetIOErrorHandler(winClipboardIOErrorHandler);
157      fErrorHandlerSet = TRUE;
158    }
159
160    /* Set jump point for Error exits */
161    if (setjmp(g_jmpEntry)) {
162        ErrorF("winClipboardProc - setjmp returned for IO Error Handler.\n");
163        goto winClipboardProc_Done;
164    }
165
166    /* Make sure that the display opened */
167    pDisplay = XOpenDisplay(szDisplay);
168    if (pDisplay == NULL) {
169        ErrorF("winClipboardProc - Failed opening the display, giving up\n");
170        goto winClipboardProc_Done;
171    }
172
173    ErrorF("winClipboardProc - XOpenDisplay () returned and "
174           "successfully opened the display.\n");
175
176    /* Get our connection number */
177    iConnectionNumber = ConnectionNumber(pDisplay);
178
179#ifdef HAS_DEVWINDOWS
180    /* Open a file descriptor for the windows message queue */
181    fdMessageQueue = open(WIN_MSG_QUEUE_FNAME, O_RDONLY);
182    if (fdMessageQueue == -1) {
183        ErrorF("winClipboardProc - Failed opening %s\n", WIN_MSG_QUEUE_FNAME);
184        goto winClipboardProc_Done;
185    }
186
187    /* Find max of our file descriptors */
188    iMaxDescriptor = MAX(fdMessageQueue, iConnectionNumber) + 1;
189#else
190    iMaxDescriptor = iConnectionNumber + 1;
191#endif
192
193    if (!XFixesQueryExtension(pDisplay, &xfixes_event_base, &xfixes_error_base))
194      ErrorF ("winClipboardProc - XFixes extension not present\n");
195
196    /* Create atoms */
197    atoms.atomClipboard = XInternAtom(pDisplay, "CLIPBOARD", False);
198    atoms.atomLocalProperty = XInternAtom (pDisplay, "CYGX_CUT_BUFFER", False);
199    atoms.atomUTF8String = XInternAtom (pDisplay, "UTF8_STRING", False);
200    atoms.atomCompoundText = XInternAtom (pDisplay, "COMPOUND_TEXT", False);
201    atoms.atomTargets = XInternAtom (pDisplay, "TARGETS", False);
202
203    /* Create a messaging window */
204    iWindow = XCreateSimpleWindow(pDisplay,
205                                  DefaultRootWindow(pDisplay),
206                                  1, 1,
207                                  500, 500,
208                                  0,
209                                  BlackPixel(pDisplay, 0),
210                                  BlackPixel(pDisplay, 0));
211    if (iWindow == 0) {
212        ErrorF("winClipboardProc - Could not create an X window.\n");
213        goto winClipboardProc_Done;
214    }
215
216    XStoreName(pDisplay, iWindow, "xwinclip");
217
218    /* Select event types to watch */
219    if (XSelectInput(pDisplay, iWindow, PropertyChangeMask) == BadWindow)
220        ErrorF("winClipboardProc - XSelectInput generated BadWindow "
221               "on messaging window\n");
222
223    XFixesSelectSelectionInput (pDisplay,
224                                iWindow,
225                                XA_PRIMARY,
226                                XFixesSetSelectionOwnerNotifyMask |
227                                XFixesSelectionWindowDestroyNotifyMask |
228                                XFixesSelectionClientCloseNotifyMask);
229
230    XFixesSelectSelectionInput (pDisplay,
231                                iWindow,
232                                atoms.atomClipboard,
233                                XFixesSetSelectionOwnerNotifyMask |
234                                XFixesSelectionWindowDestroyNotifyMask |
235                                XFixesSelectionClientCloseNotifyMask);
236
237
238    /* Initialize monitored selection state */
239    winClipboardInitMonitoredSelections();
240    /* Create Windows messaging window */
241    hwnd = winClipboardCreateMessagingWindow(pDisplay, iWindow, &atoms);
242
243    /* Save copy of HWND */
244    g_hwndClipboard = hwnd;
245
246    /* Assert ownership of selections if Win32 clipboard is owned */
247    if (NULL != GetClipboardOwner()) {
248        /* PRIMARY */
249        iReturn = XSetSelectionOwner(pDisplay, XA_PRIMARY,
250                                     iWindow, CurrentTime);
251        if (iReturn == BadAtom || iReturn == BadWindow ||
252            XGetSelectionOwner(pDisplay, XA_PRIMARY) != iWindow) {
253            ErrorF("winClipboardProc - Could not set PRIMARY owner\n");
254            goto winClipboardProc_Done;
255        }
256
257        /* CLIPBOARD */
258        iReturn = XSetSelectionOwner(pDisplay, atoms.atomClipboard,
259                                     iWindow, CurrentTime);
260        if (iReturn == BadAtom || iReturn == BadWindow ||
261            XGetSelectionOwner(pDisplay, atoms.atomClipboard) != iWindow) {
262            ErrorF("winClipboardProc - Could not set CLIPBOARD owner\n");
263            goto winClipboardProc_Done;
264        }
265    }
266
267    data.fUseUnicode = fUseUnicode;
268
269    /* Loop for events */
270    while (1) {
271
272        /* Process X events */
273        winClipboardFlushXEvents(hwnd,
274                                 iWindow, pDisplay, &data, &atoms);
275
276        /* Process Windows messages */
277        if (!winClipboardFlushWindowsMessageQueue(hwnd)) {
278          ErrorF("winClipboardProc - winClipboardFlushWindowsMessageQueue trapped "
279                       "WM_QUIT message, exiting main loop.\n");
280          break;
281        }
282
283        /* We need to ensure that all pending requests are sent */
284        XFlush(pDisplay);
285
286        /* Setup the file descriptor set */
287        /*
288         * NOTE: You have to do this before every call to select
289         *       because select modifies the mask to indicate
290         *       which descriptors are ready.
291         */
292        FD_ZERO(&fdsRead);
293        FD_SET(iConnectionNumber, &fdsRead);
294#ifdef HAS_DEVWINDOWS
295        FD_SET(fdMessageQueue, &fdsRead);
296#else
297        tvTimeout.tv_sec = 0;
298        tvTimeout.tv_usec = 100;
299#endif
300
301        /* Wait for a Windows event or an X event */
302        iReturn = select(iMaxDescriptor,        /* Highest fds number */
303                         &fdsRead,      /* Read mask */
304                         NULL,  /* No write mask */
305                         NULL,  /* No exception mask */
306#ifdef HAS_DEVWINDOWS
307                         NULL   /* No timeout */
308#else
309                         &tvTimeout     /* Set timeout */
310#endif
311            );
312
313#ifndef HAS_WINSOCK
314        iSelectError = errno;
315#else
316        iSelectError = WSAGetLastError();
317#endif
318
319        if (iReturn < 0) {
320#ifndef HAS_WINSOCK
321            if (iSelectError == EINTR)
322#else
323            if (iSelectError == WSAEINTR)
324#endif
325                continue;
326
327            ErrorF("winClipboardProc - Call to select () failed: %d.  "
328                   "Bailing.\n", iReturn);
329            break;
330        }
331
332        if (FD_ISSET(iConnectionNumber, &fdsRead)) {
333            winDebug
334                ("winClipboardProc - X connection ready, pumping X event queue\n");
335        }
336
337#ifdef HAS_DEVWINDOWS
338        /* Check for Windows event ready */
339        if (FD_ISSET(fdMessageQueue, &fdsRead))
340#else
341        if (1)
342#endif
343        {
344            winDebug
345                ("winClipboardProc - /dev/windows ready, pumping Windows message queue\n");
346        }
347
348#ifdef HAS_DEVWINDOWS
349        if (!(FD_ISSET(iConnectionNumber, &fdsRead)) &&
350            !(FD_ISSET(fdMessageQueue, &fdsRead))) {
351            winDebug("winClipboardProc - Spurious wake, select() returned %d\n", iReturn);
352        }
353#endif
354    }
355
356 winClipboardProc_Exit:
357    /* broke out of while loop on a shutdown message */
358    fShutdown = TRUE;
359
360 winClipboardProc_Done:
361    /* Close our Windows window */
362    if (g_hwndClipboard) {
363        DestroyWindow(g_hwndClipboard);
364    }
365
366    /* Close our X window */
367    if (pDisplay && iWindow) {
368        iReturn = XDestroyWindow(pDisplay, iWindow);
369        if (iReturn == BadWindow)
370            ErrorF("winClipboardProc - XDestroyWindow returned BadWindow.\n");
371        else
372            ErrorF("winClipboardProc - XDestroyWindow succeeded.\n");
373    }
374
375#ifdef HAS_DEVWINDOWS
376    /* Close our Win32 message handle */
377    if (fdMessageQueue)
378        close(fdMessageQueue);
379#endif
380
381#if 0
382    /*
383     * FIXME: XCloseDisplay hangs if we call it
384     *
385     * XCloseDisplay() calls XSync(), so any outstanding errors are reported.
386     * If we are built into the server, this can deadlock if the server is
387     * in the process of exiting and waiting for this thread to exit.
388     */
389
390    /* Discard any remaining events */
391    XSync(pDisplay, TRUE);
392
393    /* Select event types to watch */
394    XSelectInput(pDisplay, DefaultRootWindow(pDisplay), None);
395
396    /* Close our X display */
397    if (pDisplay) {
398        XCloseDisplay(pDisplay);
399    }
400#endif
401
402    /* global clipboard variable reset */
403    g_hwndClipboard = NULL;
404
405    return fShutdown;
406}
407
408/*
409 * Create the Windows window that we use to receive Windows messages
410 */
411
412static HWND
413winClipboardCreateMessagingWindow(Display *pDisplay, Window iWindow, ClipboardAtoms *atoms)
414{
415    WNDCLASSEX wc;
416    ClipboardWindowCreationParams cwcp;
417    HWND hwnd;
418
419    /* Setup our window class */
420    wc.cbSize = sizeof(WNDCLASSEX);
421    wc.style = CS_HREDRAW | CS_VREDRAW;
422    wc.lpfnWndProc = winClipboardWindowProc;
423    wc.cbClsExtra = 0;
424    wc.cbWndExtra = 0;
425    wc.hInstance = GetModuleHandle(NULL);
426    wc.hIcon = 0;
427    wc.hCursor = 0;
428    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
429    wc.lpszMenuName = NULL;
430    wc.lpszClassName = WIN_CLIPBOARD_WINDOW_CLASS;
431    wc.hIconSm = 0;
432    RegisterClassEx(&wc);
433
434    /* Information to be passed to WM_CREATE */
435    cwcp.pClipboardDisplay = pDisplay;
436    cwcp.iClipboardWindow = iWindow;
437    cwcp.atoms = atoms;
438
439    /* Create the window */
440    hwnd = CreateWindowExA(0,   /* Extended styles */
441                           WIN_CLIPBOARD_WINDOW_CLASS,  /* Class name */
442                           WIN_CLIPBOARD_WINDOW_TITLE,  /* Window name */
443                           WS_OVERLAPPED,       /* Not visible anyway */
444                           CW_USEDEFAULT,       /* Horizontal position */
445                           CW_USEDEFAULT,       /* Vertical position */
446                           CW_USEDEFAULT,       /* Right edge */
447                           CW_USEDEFAULT,       /* Bottom edge */
448                           (HWND) NULL, /* No parent or owner window */
449                           (HMENU) NULL,        /* No menu */
450                           GetModuleHandle(NULL),       /* Instance handle */
451                           &cwcp);       /* Creation data */
452    assert(hwnd != NULL);
453
454    /* I'm not sure, but we may need to call this to start message processing */
455    ShowWindow(hwnd, SW_HIDE);
456
457    /* Similarly, we may need a call to this even though we don't paint */
458    UpdateWindow(hwnd);
459
460    return hwnd;
461}
462
463/*
464 * winClipboardErrorHandler - Our application specific error handler
465 */
466
467static int
468winClipboardErrorHandler(Display * pDisplay, XErrorEvent * pErr)
469{
470    char pszErrorMsg[100];
471
472    XGetErrorText(pDisplay, pErr->error_code, pszErrorMsg, sizeof(pszErrorMsg));
473    ErrorF("winClipboardErrorHandler - ERROR: \n\t%s\n"
474           "\tSerial: %lu, Request Code: %d, Minor Code: %d\n",
475           pszErrorMsg, pErr->serial, pErr->request_code, pErr->minor_code);
476    return 0;
477}
478
479/*
480 * winClipboardIOErrorHandler - Our application specific IO error handler
481 */
482
483static int
484winClipboardIOErrorHandler(Display * pDisplay)
485{
486    ErrorF("winClipboardIOErrorHandler!\n");
487
488    if (pthread_equal(pthread_self(), g_winClipboardProcThread)) {
489        /* Restart at the main entry point */
490        longjmp(g_jmpEntry, 2);
491    }
492
493    if (g_winClipboardOldIOErrorHandler)
494        g_winClipboardOldIOErrorHandler(pDisplay);
495
496    return 0;
497}
498
499void
500winClipboardWindowDestroy(void)
501{
502  if (g_hwndClipboard) {
503    SendMessage(g_hwndClipboard, WM_WM_QUIT, 0, 0);
504  }
505}
506
507void
508winFixClipboardChain(void)
509{
510  if (g_hwndClipboard) {
511    PostMessage(g_hwndClipboard, WM_WM_REINIT, 0, 0);
512  }
513}
514