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#include <assert.h> 40#include <unistd.h> 41#include <fcntl.h> 42#include <pthread.h> 43#include <sys/param.h> // for MAX() macro 44 45#ifdef HAS_WINSOCK 46#include <X11/Xwinsock.h> 47#else 48#include <errno.h> 49#endif 50 51#include <xcb/xcb.h> 52#include <xcb/xcb_aux.h> 53#include <xcb/xcb_icccm.h> 54#include <xcb/xfixes.h> 55 56#include "winclipboard.h" 57#include "internal.h" 58 59#define WIN_CONNECT_RETRIES 40 60#define WIN_CONNECT_DELAY 4 61 62#define WIN_CLIPBOARD_WINDOW_CLASS "xwinclip" 63#define WIN_CLIPBOARD_WINDOW_TITLE "xwinclip" 64#ifdef HAS_DEVWINDOWS 65#define WIN_MSG_QUEUE_FNAME "/dev/windows" 66#endif 67 68/* 69 * Global variables 70 */ 71 72static HWND g_hwndClipboard = NULL; 73 74int xfixes_event_base; 75int xfixes_error_base; 76 77/* 78 * Local function prototypes 79 */ 80 81static HWND 82winClipboardCreateMessagingWindow(xcb_connection_t *conn, xcb_window_t iWindow, ClipboardAtoms *atoms); 83 84static xcb_atom_t 85intern_atom(xcb_connection_t *conn, const char *atomName) 86{ 87 xcb_intern_atom_reply_t *atom_reply; 88 xcb_intern_atom_cookie_t atom_cookie; 89 xcb_atom_t atom = XCB_ATOM_NONE; 90 91 atom_cookie = xcb_intern_atom(conn, 0, strlen(atomName), atomName); 92 atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); 93 if (atom_reply) { 94 atom = atom_reply->atom; 95 free(atom_reply); 96 } 97 return atom; 98} 99 100/* 101 * Create X11 and Win32 messaging windows, and run message processing loop 102 * 103 * returns TRUE if shutdown was signalled to loop, FALSE if some error occurred 104 */ 105 106BOOL 107winClipboardProc(char *szDisplay, xcb_auth_info_t *auth_info) 108{ 109 ClipboardAtoms atoms; 110 int iReturn; 111 HWND hwnd = NULL; 112 int iConnectionNumber = 0; 113#ifdef HAS_DEVWINDOWS 114 int fdMessageQueue = 0; 115#else 116 struct timeval tvTimeout; 117#endif 118 fd_set fdsRead; 119 int iMaxDescriptor; 120 xcb_connection_t *conn; 121 xcb_window_t iWindow = XCB_NONE; 122 int iSelectError; 123 BOOL fShutdown = FALSE; 124 ClipboardConversionData data; 125 int screen; 126 127 winDebug("winClipboardProc - Hello\n"); 128 129 /* Make sure that the display opened */ 130 conn = xcb_connect_to_display_with_auth_info(szDisplay, auth_info, &screen); 131 if (xcb_connection_has_error(conn)) { 132 ErrorF("winClipboardProc - Failed opening the display, giving up\n"); 133 goto winClipboardProc_Done; 134 } 135 136 ErrorF("winClipboardProc - xcb_connect () returned and " 137 "successfully opened the display.\n"); 138 139 /* Get our connection number */ 140 iConnectionNumber = xcb_get_file_descriptor(conn); 141 142#ifdef HAS_DEVWINDOWS 143 /* Open a file descriptor for the windows message queue */ 144 fdMessageQueue = open(WIN_MSG_QUEUE_FNAME, O_RDONLY); 145 if (fdMessageQueue == -1) { 146 ErrorF("winClipboardProc - Failed opening %s\n", WIN_MSG_QUEUE_FNAME); 147 goto winClipboardProc_Done; 148 } 149 150 /* Find max of our file descriptors */ 151 iMaxDescriptor = MAX(fdMessageQueue, iConnectionNumber) + 1; 152#else 153 iMaxDescriptor = iConnectionNumber + 1; 154#endif 155 156 const xcb_query_extension_reply_t *xfixes_query; 157 xfixes_query = xcb_get_extension_data(conn, &xcb_xfixes_id); 158 if (!xfixes_query->present) 159 ErrorF ("winClipboardProc - XFixes extension not present\n"); 160 xfixes_event_base = xfixes_query->first_event; 161 xfixes_error_base = xfixes_query->first_error; 162 /* Must advise server of XFIXES version we require */ 163 xcb_xfixes_query_version_unchecked(conn, 1, 0); 164 165 /* Create atoms */ 166 atoms.atomClipboard = intern_atom(conn, "CLIPBOARD"); 167 atoms.atomLocalProperty = intern_atom(conn, "CYGX_CUT_BUFFER"); 168 atoms.atomUTF8String = intern_atom(conn, "UTF8_STRING"); 169 atoms.atomCompoundText = intern_atom(conn, "COMPOUND_TEXT"); 170 atoms.atomTargets = intern_atom(conn, "TARGETS"); 171 atoms.atomIncr = intern_atom(conn, "INCR"); 172 173 xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); 174 xcb_window_t root_window_id = root_screen->root; 175 176 /* Create a messaging window */ 177 iWindow = xcb_generate_id(conn); 178 xcb_void_cookie_t cookie = xcb_create_window_checked(conn, 179 XCB_COPY_FROM_PARENT, 180 iWindow, 181 root_window_id, 182 1, 1, 183 500, 500, 184 0, 185 XCB_WINDOW_CLASS_INPUT_ONLY, 186 XCB_COPY_FROM_PARENT, 187 0, 188 NULL); 189 190 xcb_generic_error_t *error; 191 if ((error = xcb_request_check(conn, cookie))) { 192 ErrorF("winClipboardProc - Could not create an X window.\n"); 193 free(error); 194 goto winClipboardProc_Done; 195 } 196 197 xcb_icccm_set_wm_name(conn, iWindow, XCB_ATOM_STRING, 8, strlen("xwinclip"), "xwinclip"); 198 199 /* Select event types to watch */ 200 const static uint32_t values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; 201 cookie = xcb_change_window_attributes_checked(conn, iWindow, XCB_CW_EVENT_MASK, values); 202 if ((error = xcb_request_check(conn, cookie))) { 203 ErrorF("winClipboardProc - Could not set event mask on messaging window\n"); 204 free(error); 205 } 206 207 xcb_xfixes_select_selection_input(conn, 208 iWindow, 209 XCB_ATOM_PRIMARY, 210 XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | 211 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | 212 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE); 213 214 xcb_xfixes_select_selection_input(conn, 215 iWindow, 216 atoms.atomClipboard, 217 XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | 218 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | 219 XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE); 220 221 /* Initialize monitored selection state */ 222 winClipboardInitMonitoredSelections(); 223 /* Create Windows messaging window */ 224 hwnd = winClipboardCreateMessagingWindow(conn, iWindow, &atoms); 225 226 /* Save copy of HWND */ 227 g_hwndClipboard = hwnd; 228 229 /* Assert ownership of selections if Win32 clipboard is owned */ 230 if (NULL != GetClipboardOwner()) { 231 /* PRIMARY */ 232 cookie = xcb_set_selection_owner_checked(conn, iWindow, XCB_ATOM_PRIMARY, XCB_CURRENT_TIME); 233 if ((error = xcb_request_check(conn, cookie))) { 234 ErrorF("winClipboardProc - Could not set PRIMARY owner\n"); 235 free(error); 236 goto winClipboardProc_Done; 237 } 238 239 /* CLIPBOARD */ 240 cookie = xcb_set_selection_owner_checked(conn, iWindow, atoms.atomClipboard, XCB_CURRENT_TIME); 241 if ((error = xcb_request_check(conn, cookie))) { 242 ErrorF("winClipboardProc - Could not set CLIPBOARD owner\n"); 243 free(error); 244 goto winClipboardProc_Done; 245 } 246 } 247 248 data.incr = NULL; 249 data.incrsize = 0; 250 251 /* Loop for events */ 252 while (1) { 253 254 /* Process X events */ 255 winClipboardFlushXEvents(hwnd, iWindow, conn, &data, &atoms); 256 257 /* Process Windows messages */ 258 if (!winClipboardFlushWindowsMessageQueue(hwnd)) { 259 ErrorF("winClipboardProc - winClipboardFlushWindowsMessageQueue trapped " 260 "WM_QUIT message, exiting main loop.\n"); 261 break; 262 } 263 264 /* We need to ensure that all pending requests are sent */ 265 xcb_flush(conn); 266 267 /* Setup the file descriptor set */ 268 /* 269 * NOTE: You have to do this before every call to select 270 * because select modifies the mask to indicate 271 * which descriptors are ready. 272 */ 273 FD_ZERO(&fdsRead); 274 FD_SET(iConnectionNumber, &fdsRead); 275#ifdef HAS_DEVWINDOWS 276 FD_SET(fdMessageQueue, &fdsRead); 277#else 278 tvTimeout.tv_sec = 0; 279 tvTimeout.tv_usec = 100; 280#endif 281 282 /* Wait for a Windows event or an X event */ 283 iReturn = select(iMaxDescriptor, /* Highest fds number */ 284 &fdsRead, /* Read mask */ 285 NULL, /* No write mask */ 286 NULL, /* No exception mask */ 287#ifdef HAS_DEVWINDOWS 288 NULL /* No timeout */ 289#else 290 &tvTimeout /* Set timeout */ 291#endif 292 ); 293 294#ifndef HAS_WINSOCK 295 iSelectError = errno; 296#else 297 iSelectError = WSAGetLastError(); 298#endif 299 300 if (iReturn < 0) { 301#ifndef HAS_WINSOCK 302 if (iSelectError == EINTR) 303#else 304 if (iSelectError == WSAEINTR) 305#endif 306 continue; 307 308 ErrorF("winClipboardProc - Call to select () failed: %d. " 309 "Bailing.\n", iReturn); 310 break; 311 } 312 313 if (FD_ISSET(iConnectionNumber, &fdsRead)) { 314 winDebug 315 ("winClipboardProc - X connection ready, pumping X event queue\n"); 316 } 317 318#ifdef HAS_DEVWINDOWS 319 /* Check for Windows event ready */ 320 if (FD_ISSET(fdMessageQueue, &fdsRead)) 321#else 322 if (1) 323#endif 324 { 325 winDebug 326 ("winClipboardProc - /dev/windows ready, pumping Windows message queue\n"); 327 } 328 329#ifdef HAS_DEVWINDOWS 330 if (!(FD_ISSET(iConnectionNumber, &fdsRead)) && 331 !(FD_ISSET(fdMessageQueue, &fdsRead))) { 332 winDebug("winClipboardProc - Spurious wake, select() returned %d\n", iReturn); 333 } 334#endif 335 } 336 337 /* broke out of while loop on a shutdown message */ 338 fShutdown = TRUE; 339 340 winClipboardProc_Done: 341 /* Close our Windows window */ 342 if (g_hwndClipboard) { 343 DestroyWindow(g_hwndClipboard); 344 } 345 346 /* Close our X window */ 347 if (!xcb_connection_has_error(conn) && iWindow) { 348 cookie = xcb_destroy_window_checked(conn, iWindow); 349 if ((error = xcb_request_check(conn, cookie))) 350 ErrorF("winClipboardProc - XDestroyWindow failed.\n"); 351 else 352 ErrorF("winClipboardProc - XDestroyWindow succeeded.\n"); 353 free(error); 354 } 355 356#ifdef HAS_DEVWINDOWS 357 /* Close our Win32 message handle */ 358 if (fdMessageQueue) 359 close(fdMessageQueue); 360#endif 361 362 /* 363 * xcb_disconnect() does not sync, so is safe to call even when we are built 364 * into the server. Unlike XCloseDisplay() there will be no deadlock if the 365 * server is in the process of exiting and waiting for this thread to exit. 366 */ 367 if (!xcb_connection_has_error(conn)) { 368 /* Close our X display */ 369 xcb_disconnect(conn); 370 } 371 372 /* global clipboard variable reset */ 373 g_hwndClipboard = NULL; 374 375 return fShutdown; 376} 377 378/* 379 * Create the Windows window that we use to receive Windows messages 380 */ 381 382static HWND 383winClipboardCreateMessagingWindow(xcb_connection_t *conn, xcb_window_t iWindow, ClipboardAtoms *atoms) 384{ 385 WNDCLASSEX wc; 386 ClipboardWindowCreationParams cwcp; 387 HWND hwnd; 388 389 /* Setup our window class */ 390 wc.cbSize = sizeof(WNDCLASSEX); 391 wc.style = CS_HREDRAW | CS_VREDRAW; 392 wc.lpfnWndProc = winClipboardWindowProc; 393 wc.cbClsExtra = 0; 394 wc.cbWndExtra = 0; 395 wc.hInstance = GetModuleHandle(NULL); 396 wc.hIcon = 0; 397 wc.hCursor = 0; 398 wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); 399 wc.lpszMenuName = NULL; 400 wc.lpszClassName = WIN_CLIPBOARD_WINDOW_CLASS; 401 wc.hIconSm = 0; 402 RegisterClassEx(&wc); 403 404 /* Information to be passed to WM_CREATE */ 405 cwcp.pClipboardDisplay = conn; 406 cwcp.iClipboardWindow = iWindow; 407 cwcp.atoms = atoms; 408 409 /* Create the window */ 410 hwnd = CreateWindowExA(0, /* Extended styles */ 411 WIN_CLIPBOARD_WINDOW_CLASS, /* Class name */ 412 WIN_CLIPBOARD_WINDOW_TITLE, /* Window name */ 413 WS_OVERLAPPED, /* Not visible anyway */ 414 CW_USEDEFAULT, /* Horizontal position */ 415 CW_USEDEFAULT, /* Vertical position */ 416 CW_USEDEFAULT, /* Right edge */ 417 CW_USEDEFAULT, /* Bottom edge */ 418 (HWND) NULL, /* No parent or owner window */ 419 (HMENU) NULL, /* No menu */ 420 GetModuleHandle(NULL), /* Instance handle */ 421 &cwcp); /* Creation data */ 422 assert(hwnd != NULL); 423 424 /* I'm not sure, but we may need to call this to start message processing */ 425 ShowWindow(hwnd, SW_HIDE); 426 427 /* Similarly, we may need a call to this even though we don't paint */ 428 UpdateWindow(hwnd); 429 430 return hwnd; 431} 432 433void 434winClipboardWindowDestroy(void) 435{ 436 if (g_hwndClipboard) { 437 SendMessage(g_hwndClipboard, WM_WM_QUIT, 0, 0); 438 } 439} 440