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