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