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
79 static HWND g_hwndClipboard = NULL;
80 static jmp_buf g_jmpEntry;
81 static XIOErrorHandler g_winClipboardOldIOErrorHandler;
82 static pthread_t g_winClipboardProcThread;
83
84 int xfixes_event_base;
85 int xfixes_error_base;
86
87 Bool g_fHasModernClipboardApi = FALSE;
88 ADDCLIPBOARDFORMATLISTENERPROC g_fpAddClipboardFormatListener;
89 REMOVECLIPBOARDFORMATLISTENERPROC g_fpRemoveClipboardFormatListener;
90
91 /*
92 * Local function prototypes
93 */
94
95 static HWND
96 winClipboardCreateMessagingWindow(Display *pDisplay, Window iWindow, ClipboardAtoms *atoms);
97
98 static int
99 winClipboardErrorHandler(Display * pDisplay, XErrorEvent * pErr);
100
101 static 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
110 Bool
winClipboardProc(Bool fUseUnicode,char * szDisplay)111 winClipboardProc(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
412 static HWND
winClipboardCreateMessagingWindow(Display * pDisplay,Window iWindow,ClipboardAtoms * atoms)413 winClipboardCreateMessagingWindow(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
467 static int
winClipboardErrorHandler(Display * pDisplay,XErrorEvent * pErr)468 winClipboardErrorHandler(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
483 static int
winClipboardIOErrorHandler(Display * pDisplay)484 winClipboardIOErrorHandler(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
499 void
winClipboardWindowDestroy(void)500 winClipboardWindowDestroy(void)
501 {
502 if (g_hwndClipboard) {
503 SendMessage(g_hwndClipboard, WM_WM_QUIT, 0, 0);
504 }
505 }
506
507 void
winFixClipboardChain(void)508 winFixClipboardChain(void)
509 {
510 if (g_hwndClipboard) {
511 PostMessage(g_hwndClipboard, WM_WM_REINIT, 0, 0);
512 }
513 }
514