VirtualBox

source: vbox/trunk/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp@ 102820

Last change on this file since 102820 was 102820, checked in by vboxsync, 12 months ago

Shared Clipboard/X11: Renaming (ShClX11ReportFormatsToX11Worker -> shClX11ReportFormatsToX11Worker()). bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 90.6 KB
Line 
1/** @file
2 * Shared Clipboard: Common X11 code.
3 */
4
5/*
6 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
7 *
8 * This file is part of VirtualBox base platform packages, as
9 * available from https://www.virtualbox.org.
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation, in version 3 of the
14 * License.
15 *
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, see <https://www.gnu.org/licenses>.
23 *
24 * SPDX-License-Identifier: GPL-3.0-only
25 */
26
27/* Note: to automatically run regression tests on the Shared Clipboard,
28 * execute the tstClipboardGH-X11 testcase. If you often make changes to the
29 * clipboard code, adding the line
30 *
31 * OTHERS += $(PATH_tstClipboardGH-X11)/tstClipboardGH-X11.run
32 *
33 * to LocalConfig.kmk will cause the tests to be run every time the code is
34 * changed.
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
42
43#include <errno.h>
44
45#include <dlfcn.h>
46#include <fcntl.h>
47#include <unistd.h>
48
49#ifdef RT_OS_SOLARIS
50#include <tsol/label.h>
51#endif
52
53#include <X11/Xlib.h>
54#include <X11/Xatom.h>
55#include <X11/Intrinsic.h>
56#include <X11/Shell.h>
57#include <X11/Xproto.h>
58#include <X11/StringDefs.h>
59
60#include <iprt/assert.h>
61#include <iprt/types.h>
62#include <iprt/mem.h>
63#include <iprt/semaphore.h>
64#include <iprt/thread.h>
65#include <iprt/utf16.h>
66#include <iprt/uri.h>
67
68#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
69# include <iprt/cpp/list.h>
70# include <iprt/cpp/ministring.h>
71# include <VBox/GuestHost/SharedClipboard-transfers.h>
72#endif
73
74#include <VBox/log.h>
75#include <VBox/version.h>
76
77#include <VBox/GuestHost/SharedClipboard.h>
78#include <VBox/GuestHost/SharedClipboard-x11.h>
79#include <VBox/GuestHost/clipboard-helper.h>
80#include <VBox/HostServices/VBoxClipboardSvc.h>
81
82/** Own macro for declaring function visibility / linkage based on whether this
83 * code runs as part of test cases or not. */
84#ifdef TESTCASE
85# define SHCL_X11_DECL(x) x
86#else
87# define SHCL_X11_DECL(x) static x
88#endif
89
90
91/*********************************************************************************************************************************
92* Externals *
93*********************************************************************************************************************************/
94#ifdef TESTCASE
95extern void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data);
96extern void tstClipRequestData(SHCLX11CTX* pCtx, SHCLX11FMTIDX target, void *closure);
97extern void tstRequestTargets(SHCLX11CTX* pCtx);
98#endif
99
100
101/*********************************************************************************************************************************
102* Prototypes *
103*********************************************************************************************************************************/
104class formats;
105SHCL_X11_DECL(Atom) clipGetAtom(PSHCLX11CTX pCtx, const char *pszName);
106SHCL_X11_DECL(void) clipQueryX11Targets(PSHCLX11CTX pCtx);
107
108static int clipInitInternal(PSHCLX11CTX pCtx);
109static void clipUninitInternal(PSHCLX11CTX pCtx);
110
111
112/*********************************************************************************************************************************
113* Global Variables *
114*********************************************************************************************************************************/
115
116/**
117 * The table maps X11 names to data formats
118 * and to the corresponding VBox clipboard formats.
119 */
120SHCL_X11_DECL(SHCLX11FMTTABLE) g_aFormats[] =
121{
122 { "INVALID", SHCLX11FMT_INVALID, VBOX_SHCL_FMT_NONE },
123
124 { "UTF8_STRING", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT },
125 { "text/plain;charset=UTF-8", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT },
126 { "text/plain;charset=utf-8", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT },
127 { "STRING", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT },
128 { "TEXT", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT },
129 { "text/plain", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT },
130
131 { "text/html", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML },
132 { "text/html;charset=utf-8", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML },
133 { "application/x-moz-nativehtml", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML },
134
135 { "image/bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP },
136 { "image/x-bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP },
137 { "image/x-MS-bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP },
138 /** @todo Inkscape exports image/png but not bmp... */
139
140#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
141 { "text/uri-list", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST },
142 { "x-special/gnome-copied-files", SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES, VBOX_SHCL_FMT_URI_LIST },
143 { "x-special/mate-copied-files", SHCLX11FMT_URI_LIST_MATE_COPIED_FILES, VBOX_SHCL_FMT_URI_LIST },
144 { "x-special/nautilus-clipboard", SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD, VBOX_SHCL_FMT_URI_LIST },
145 { "application/x-kde-cutselection", SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, VBOX_SHCL_FMT_URI_LIST },
146 /** @todo Anything else we need to add here? */
147 /** @todo Add Wayland / Weston support. */
148#endif
149};
150
151
152#ifdef TESTCASE
153# ifdef RT_OS_SOLARIS_10
154char XtStrings [] = "";
155WidgetClassRec* applicationShellWidgetClass;
156char XtShellStrings [] = "";
157int XmbTextPropertyToTextList(
158 Display* /* display */,
159 XTextProperty* /* text_prop */,
160 char*** /* list_return */,
161 int* /* count_return */
162)
163{
164 return 0;
165}
166# else /* !RT_OS_SOLARIS_10 */
167const char XtStrings [] = "";
168_WidgetClassRec* applicationShellWidgetClass;
169const char XtShellStrings [] = "";
170# endif /* RT_OS_SOLARIS_10 */
171#else /* !TESTCASE */
172# ifdef VBOX_WITH_VBOXCLIENT_LAZY_LOAD
173/* Defines needed for lazy loading global data from the shared objects (.so).
174 * See r157060. */
175DECLASM(WidgetClass * ) LazyGetPtr_applicationShellWidgetClass(void);
176#define applicationShellWidgetClass (*LazyGetPtr_applicationShellWidgetClass())
177DECLASM(const char *) LazyGetPtr_XtStrings(void);
178#define XtStrings (LazyGetPtr_XtStrings())
179# endif
180#endif /* TESTCASE */
181
182
183/*********************************************************************************************************************************
184* Defines *
185*********************************************************************************************************************************/
186
187#define SHCL_MAX_X11_FORMATS RT_ELEMENTS(g_aFormats)
188
189
190/*********************************************************************************************************************************
191* Internal structures *
192*********************************************************************************************************************************/
193
194#ifdef TESTCASE
195/**
196 * Return the max. number of elements in the X11 format table.
197 * Used by the testing code in tstClipboardGH-X11.cpp
198 * which cannot use RT_ELEMENTS(g_aFormats) directly.
199 *
200 * @return size_t The number of elements in the g_aFormats array.
201 */
202SHCL_X11_DECL(size_t) clipReportMaxX11Formats(void)
203{
204 return (RT_ELEMENTS(g_aFormats));
205}
206#endif
207
208/**
209 * Returns the atom corresponding to a supported X11 format.
210 *
211 * @returns Found atom to the corresponding X11 format.
212 * @param pCtx The X11 clipboard context to use.
213 * @param uFmtIdx Format index to look up atom for.
214 */
215static Atom clipAtomForX11Format(PSHCLX11CTX pCtx, SHCLX11FMTIDX uFmtIdx)
216{
217 AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), 0);
218 return clipGetAtom(pCtx, g_aFormats[uFmtIdx].pcszAtom);
219}
220
221/**
222 * Returns the SHCLX11FMT corresponding to a supported X11 format.
223 *
224 * @return SHCLX11FMT for a specific format index.
225 * @param uFmtIdx Format index to look up SHCLX11CLIPFMT for.
226 */
227SHCL_X11_DECL(SHCLX11FMT) clipRealFormatForX11Format(SHCLX11FMTIDX uFmtIdx)
228{
229 AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), SHCLX11FMT_INVALID);
230 return g_aFormats[uFmtIdx].enmFmtX11;
231}
232
233/**
234 * Returns the VBox format corresponding to a supported X11 format.
235 *
236 * @return SHCLFORMAT for a specific format index.
237 * @param uFmtIdx Format index to look up VBox format for.
238 */
239static SHCLFORMAT clipVBoxFormatForX11Format(SHCLX11FMTIDX uFmtIdx)
240{
241 AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), VBOX_SHCL_FMT_NONE);
242 return g_aFormats[uFmtIdx].uFmtVBox;
243}
244
245/**
246 * Looks up the X11 format matching a given X11 atom.
247 *
248 * @returns The format on success, NIL_CLIPX11FORMAT on failure.
249 * @param pCtx The X11 clipboard context to use.
250 * @param atomFormat Atom to look up X11 format for.
251 */
252static SHCLX11FMTIDX clipFindX11FormatByAtom(PSHCLX11CTX pCtx, Atom atomFormat)
253{
254 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
255 if (clipAtomForX11Format(pCtx, i) == atomFormat)
256 {
257 LogFlowFunc(("Returning index %u for atom '%s'\n", i, g_aFormats[i].pcszAtom));
258 return i;
259 }
260 return NIL_CLIPX11FORMAT;
261}
262
263/**
264 * Enumerates supported X11 clipboard formats corresponding to given VBox formats.
265 *
266 * @returns The next matching X11 format index in the list, or NIL_CLIPX11FORMAT if there are no more.
267 * @param uFormatsVBox VBox formats to enumerate supported X11 clipboard formats for.
268 * @param lastFmtIdx The value returned from the last call of this function.
269 * Use NIL_CLIPX11FORMAT to start the enumeration.
270 */
271static SHCLX11FMTIDX clipEnumX11Formats(SHCLFORMATS uFormatsVBox,
272 SHCLX11FMTIDX lastFmtIdx)
273{
274 for (unsigned i = lastFmtIdx + 1; i < RT_ELEMENTS(g_aFormats); ++i)
275 {
276 if (uFormatsVBox & clipVBoxFormatForX11Format(i))
277 return i;
278 }
279
280 return NIL_CLIPX11FORMAT;
281}
282
283/**
284 * Array of structures for mapping Xt widgets to context pointers. We
285 * need this because the widget clipboard callbacks do not pass user data.
286 */
287static struct
288{
289 /** Pointer to widget we want to associate the context with. */
290 Widget pWidget;
291 /** Pointer to X11 context associated with the widget. */
292 PSHCLX11CTX pCtx;
293} g_aContexts[VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX];
294
295/**
296 * Registers a new X11 clipboard context.
297 *
298 * @returns VBox status code.
299 * @param pCtx The X11 clipboard context to use.
300 */
301static int clipRegisterContext(PSHCLX11CTX pCtx)
302{
303 AssertPtrReturn(pCtx, VERR_INVALID_PARAMETER);
304
305 bool fFound = false;
306
307 Widget pWidget = pCtx->pWidget;
308 AssertReturn(pWidget != NULL, VERR_INVALID_PARAMETER);
309
310 for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i)
311 {
312 AssertReturn( (g_aContexts[i].pWidget != pWidget)
313 && (g_aContexts[i].pCtx != pCtx), VERR_WRONG_ORDER);
314 if (g_aContexts[i].pWidget == NULL && !fFound)
315 {
316 AssertReturn(g_aContexts[i].pCtx == NULL, VERR_INTERNAL_ERROR);
317 g_aContexts[i].pWidget = pWidget;
318 g_aContexts[i].pCtx = pCtx;
319 fFound = true;
320 }
321 }
322
323 return fFound ? VINF_SUCCESS : VERR_OUT_OF_RESOURCES;
324}
325
326/**
327 * Unregister an X11 clipboard context.
328 *
329 * @param pCtx The X11 clipboard context to use.
330 */
331static void clipUnregisterContext(PSHCLX11CTX pCtx)
332{
333 AssertPtrReturnVoid(pCtx);
334
335 Widget pWidget = pCtx->pWidget;
336 AssertPtrReturnVoid(pWidget);
337
338 bool fFound = false;
339 for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i)
340 {
341 Assert(!fFound || g_aContexts[i].pWidget != pWidget);
342 if (g_aContexts[i].pWidget == pWidget)
343 {
344 Assert(g_aContexts[i].pCtx != NULL);
345 g_aContexts[i].pWidget = NULL;
346 g_aContexts[i].pCtx = NULL;
347 fFound = true;
348 }
349 }
350}
351
352/**
353 * Finds a X11 clipboard context for a specific X11 widget.
354 *
355 * @returns Pointer to associated X11 clipboard context if found, or NULL if not found.
356 * @param pWidget X11 widget to return X11 clipboard context for.
357 */
358static PSHCLX11CTX clipLookupContext(Widget pWidget)
359{
360 AssertPtrReturn(pWidget, NULL);
361
362 for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i)
363 {
364 if (g_aContexts[i].pWidget == pWidget)
365 {
366 Assert(g_aContexts[i].pCtx != NULL);
367 return g_aContexts[i].pCtx;
368 }
369 }
370
371 return NULL;
372}
373
374/**
375 * Converts an atom name string to an X11 atom, looking it up in a cache before asking the server.
376 *
377 * @returns Found X11 atom.
378 * @param pCtx The X11 clipboard context to use.
379 * @param pcszName Name of atom to return atom for.
380 */
381SHCL_X11_DECL(Atom) clipGetAtom(PSHCLX11CTX pCtx, const char *pcszName)
382{
383 AssertPtrReturn(pcszName, None);
384 return XInternAtom(XtDisplay(pCtx->pWidget), pcszName, False);
385}
386
387/** String written to the wakeup pipe. */
388#define WAKE_UP_STRING "WakeUp!"
389/** Length of the string written. */
390#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
391
392/**
393 * Schedules a function call to run on the Xt event thread by passing it to
394 * the application context as a 0ms timeout and waking up the event loop by
395 * writing to the wakeup pipe which it monitors.
396 */
397static int clipThreadScheduleCall(PSHCLX11CTX pCtx,
398 void (*proc)(void *, void *),
399 void *client_data)
400{
401 LogFlowFunc(("proc=%p, client_data=%p\n", proc, client_data));
402
403#ifndef TESTCASE
404 AssertReturn(pCtx, VERR_INVALID_POINTER);
405 AssertReturn(pCtx->pAppContext, VERR_INVALID_POINTER);
406
407 XtAppAddTimeOut(pCtx->pAppContext, 0, (XtTimerCallbackProc)proc,
408 (XtPointer)client_data);
409 ssize_t cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN);
410 Assert(cbWritten == WAKE_UP_STRING_LEN);
411 RT_NOREF(cbWritten);
412#else
413 RT_NOREF(pCtx);
414 tstThreadScheduleCall(proc, client_data);
415#endif
416
417 LogFlowFuncLeaveRC(VINF_SUCCESS);
418 return VINF_SUCCESS;
419}
420
421/**
422 * Reports the formats currently supported by the X11 clipboard to VBox.
423 *
424 * @note Runs in Xt event thread.
425 *
426 * @param pCtx The X11 clipboard context to use.
427 */
428static void clipReportFormatsToVBox(PSHCLX11CTX pCtx)
429{
430 SHCLFORMATS vboxFmt = clipVBoxFormatForX11Format(pCtx->idxFmtText);
431 vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtBmp);
432 vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtHTML);
433#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
434 vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtURI);
435#endif
436
437 LogFlowFunc(("idxFmtText=%u ('%s'), idxFmtBmp=%u ('%s'), idxFmtHTML=%u ('%s')",
438 pCtx->idxFmtText, g_aFormats[pCtx->idxFmtText].pcszAtom,
439 pCtx->idxFmtBmp, g_aFormats[pCtx->idxFmtBmp].pcszAtom,
440 pCtx->idxFmtHTML, g_aFormats[pCtx->idxFmtHTML].pcszAtom));
441#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
442 Log((", idxFmtURI=%u ('%s')", pCtx->idxFmtURI, g_aFormats[pCtx->idxFmtURI].pcszAtom));
443#endif
444 Log((" -> vboxFmt=%#x\n", vboxFmt));
445
446#ifdef LOG_ENABLED
447 char *pszFmts = ShClFormatsToStrA(vboxFmt);
448 AssertPtrReturnVoid(pszFmts);
449 LogRel2(("Shared Clipboard: X11 reported available VBox formats '%s'\n", pszFmts));
450 RTStrFree(pszFmts);
451#endif
452
453 if (pCtx->Callbacks.pfnReportFormats)
454 pCtx->Callbacks.pfnReportFormats(pCtx->pFrontend, vboxFmt, NULL /* pvUser */);
455}
456
457/**
458 * Forgets which formats were previously in the X11 clipboard. Called when we
459 * grab the clipboard.
460 *
461 * @param pCtx The X11 clipboard context to use.
462 */
463static void clipResetX11Formats(PSHCLX11CTX pCtx)
464{
465 LogFlowFuncEnter();
466
467 pCtx->idxFmtText = 0;
468 pCtx->idxFmtBmp = 0;
469 pCtx->idxFmtHTML = 0;
470#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
471 pCtx->idxFmtURI = 0;
472#endif
473}
474
475/**
476 * Tells VBox that X11 currently has nothing in its clipboard.
477 *
478 * @param pCtx The X11 clipboard context to use.
479 */
480SHCL_X11_DECL(void) clipReportEmpty(PSHCLX11CTX pCtx)
481{
482 clipResetX11Formats(pCtx);
483 clipReportFormatsToVBox(pCtx);
484}
485
486/**
487 * Go through an array of X11 clipboard targets to see if they contain a text
488 * format we can support, and if so choose the ones we prefer (e.g. we like
489 * UTF-8 better than plain text).
490 *
491 * @return Index to supported X clipboard format.
492 * @param pCtx The X11 clipboard context to use.
493 * @param paIdxFmtTargets The list of targets.
494 * @param cTargets The size of the list in @a pTargets.
495 */
496SHCL_X11_DECL(SHCLX11FMTIDX) clipGetTextFormatFromTargets(PSHCLX11CTX pCtx,
497 SHCLX11FMTIDX *paIdxFmtTargets,
498 size_t cTargets)
499{
500 AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
501 AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
502
503 SHCLX11FMTIDX idxFmtText = NIL_CLIPX11FORMAT;
504 SHCLX11FMT fmtTextX11 = SHCLX11FMT_INVALID;
505 for (unsigned i = 0; i < cTargets; ++i)
506 {
507 SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i];
508 if (idxFmt != NIL_CLIPX11FORMAT)
509 {
510 if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_UNICODETEXT)
511 && fmtTextX11 < clipRealFormatForX11Format(idxFmt))
512 {
513 fmtTextX11 = clipRealFormatForX11Format(idxFmt);
514 idxFmtText = idxFmt;
515 }
516 }
517 }
518 return idxFmtText;
519}
520
521/**
522 * Goes through an array of X11 clipboard targets to see if they contain a bitmap
523 * format we can support, and if so choose the ones we prefer (e.g. we like
524 * BMP better than PNG because we don't have to convert).
525 *
526 * @return Supported X clipboard format.
527 * @param pCtx The X11 clipboard context to use.
528 * @param paIdxFmtTargets The list of targets.
529 * @param cTargets The size of the list in @a pTargets.
530 */
531static SHCLX11FMTIDX clipGetBitmapFormatFromTargets(PSHCLX11CTX pCtx,
532 SHCLX11FMTIDX *paIdxFmtTargets,
533 size_t cTargets)
534{
535 AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
536 AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
537
538 SHCLX11FMTIDX idxFmtBmp = NIL_CLIPX11FORMAT;
539 SHCLX11FMT fmtBmpX11 = SHCLX11FMT_INVALID;
540 for (unsigned i = 0; i < cTargets; ++i)
541 {
542 SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i];
543 if (idxFmt != NIL_CLIPX11FORMAT)
544 {
545 if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_BITMAP)
546 && fmtBmpX11 < clipRealFormatForX11Format(idxFmt))
547 {
548 fmtBmpX11 = clipRealFormatForX11Format(idxFmt);
549 idxFmtBmp = idxFmt;
550 }
551 }
552 }
553 return idxFmtBmp;
554}
555
556/**
557 * Goes through an array of X11 clipboard targets to see if they contain a HTML
558 * format we can support, and if so choose the ones we prefer.
559 *
560 * @return Supported X clipboard format.
561 * @param pCtx The X11 clipboard context to use.
562 * @param paIdxFmtTargets The list of targets.
563 * @param cTargets The size of the list in @a pTargets.
564 */
565static SHCLX11FMTIDX clipGetHtmlFormatFromTargets(PSHCLX11CTX pCtx,
566 SHCLX11FMTIDX *paIdxFmtTargets,
567 size_t cTargets)
568{
569 AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
570 AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
571
572 SHCLX11FMTIDX idxFmtHTML = NIL_CLIPX11FORMAT;
573 SHCLX11FMT fmxHTMLX11 = SHCLX11FMT_INVALID;
574 for (unsigned i = 0; i < cTargets; ++i)
575 {
576 SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i];
577 if (idxFmt != NIL_CLIPX11FORMAT)
578 {
579 if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_HTML)
580 && fmxHTMLX11 < clipRealFormatForX11Format(idxFmt))
581 {
582 fmxHTMLX11 = clipRealFormatForX11Format(idxFmt);
583 idxFmtHTML = idxFmt;
584 }
585 }
586 }
587 return idxFmtHTML;
588}
589
590# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
591/**
592 * Goes through an array of X11 clipboard targets to see if they contain an URI list
593 * format we can support, and if so choose the ones we prefer.
594 *
595 * @return Supported X clipboard format.
596 * @param pCtx The X11 clipboard context to use.
597 * @param paIdxFmtTargets The list of targets.
598 * @param cTargets The size of the list in @a pTargets.
599 */
600static SHCLX11FMTIDX clipGetURIListFormatFromTargets(PSHCLX11CTX pCtx,
601 SHCLX11FMTIDX *paIdxFmtTargets,
602 size_t cTargets)
603{
604 AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
605 AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
606
607 SHCLX11FMTIDX idxFmtURI = NIL_CLIPX11FORMAT;
608 SHCLX11FMT fmtURIX11 = SHCLX11FMT_INVALID;
609 for (unsigned i = 0; i < cTargets; ++i)
610 {
611 SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i];
612 if (idxFmt != NIL_CLIPX11FORMAT)
613 {
614 if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_URI_LIST)
615 && fmtURIX11 < clipRealFormatForX11Format(idxFmt))
616 {
617 fmtURIX11 = clipRealFormatForX11Format(idxFmt);
618 idxFmtURI = idxFmt;
619 }
620 }
621 }
622 return idxFmtURI;
623}
624# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
625
626/**
627 * Goes through an array of X11 clipboard targets to see if we can support any
628 * of them and if relevant to choose the ones we prefer (e.g. we like Utf8
629 * better than plain text).
630 *
631 * @param pCtx The X11 clipboard context to use.
632 * @param paIdxFmtTargets The list of targets.
633 * @param cTargets The size of the list in @a pTargets.
634 */
635static void clipGetFormatsFromTargets(PSHCLX11CTX pCtx,
636 SHCLX11FMTIDX *paIdxFmtTargets, size_t cTargets)
637{
638 AssertPtrReturnVoid(pCtx);
639 AssertPtrReturnVoid(paIdxFmtTargets);
640
641 SHCLX11FMTIDX idxFmtText = clipGetTextFormatFromTargets(pCtx, paIdxFmtTargets, cTargets);
642 if (pCtx->idxFmtText != idxFmtText)
643 pCtx->idxFmtText = idxFmtText;
644
645 pCtx->idxFmtBmp = SHCLX11FMT_INVALID; /* not yet supported */ /** @todo r=andy Check this. */
646 SHCLX11FMTIDX idxFmtBmp = clipGetBitmapFormatFromTargets(pCtx, paIdxFmtTargets, cTargets);
647 if (pCtx->idxFmtBmp != idxFmtBmp)
648 pCtx->idxFmtBmp = idxFmtBmp;
649
650 SHCLX11FMTIDX idxFmtHTML = clipGetHtmlFormatFromTargets(pCtx, paIdxFmtTargets, cTargets);
651 if (pCtx->idxFmtHTML != idxFmtHTML)
652 pCtx->idxFmtHTML = idxFmtHTML;
653
654#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
655 SHCLX11FMTIDX idxFmtURI = clipGetURIListFormatFromTargets(pCtx, paIdxFmtTargets, cTargets);
656 if (pCtx->idxFmtURI != idxFmtURI)
657 pCtx->idxFmtURI = idxFmtURI;
658#endif
659}
660
661#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
662DECLINLINE(bool) clipGetXtBusy(PSHCLX11CTX pCtx)
663{
664 LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate));
665 return pCtx->fXtBusy;
666}
667
668DECLINLINE(bool) clipGetXtNeedsUpdate(PSHCLX11CTX pCtx)
669{
670 LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate));
671 return pCtx->fXtNeedsUpdate;
672}
673
674DECLINLINE(bool) clipSetXtBusy(PSHCLX11CTX pCtx, bool fBusy)
675{
676 pCtx->fXtBusy = fBusy;
677 LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate));
678 return pCtx->fXtBusy;
679}
680
681DECLINLINE(bool) clipSetXtNeedsUpdate(PSHCLX11CTX pCtx, bool fNeedsUpdate)
682{
683 pCtx->fXtNeedsUpdate = fNeedsUpdate;
684 LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate));
685 return pCtx->fXtNeedsUpdate;
686}
687#endif /* VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY */
688
689/**
690 * Updates the context's information about targets currently supported by X11,
691 * based on an array of X11 atoms.
692 *
693 * @param pCtx The X11 clipboard context to use.
694 * @param pTargets The array of atoms describing the targets supported.
695 * @param cTargets The size of the array @a pTargets.
696 */
697SHCL_X11_DECL(void) clipUpdateX11Targets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *paIdxFmtTargets, size_t cTargets)
698{
699 LogFlowFuncEnter();
700
701#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
702 clipSetXtBusy(pCtx, false);
703 if (clipGetXtNeedsUpdate(pCtx))
704 {
705 /* We may already be out of date. */
706 clipSetXtNeedsUpdate(pCtx, false);
707 clipQueryX11Targets(pCtx);
708 return;
709 }
710#endif
711
712 if (paIdxFmtTargets == NULL)
713 {
714 /* No data available */
715 clipReportEmpty(pCtx);
716 return;
717 }
718
719 clipGetFormatsFromTargets(pCtx, paIdxFmtTargets, cTargets);
720 clipReportFormatsToVBox(pCtx);
721}
722
723/**
724 * Notifies the VBox clipboard about available data formats ("targets" on X11),
725 * based on the information obtained from the X11 clipboard.
726 *
727 * @note Callback installed by clipQueryX11Targets() for XtGetSelectionValue().
728 * @note This function is treated as API glue, and as such is not part of any
729 * unit test. So keep it simple, be paranoid and log everything.
730 */
731SHCL_X11_DECL(void) clipQueryX11TargetsCallback(Widget widget, XtPointer pClient,
732 Atom * /* selection */, Atom *atomType,
733 XtPointer pValue, long unsigned int *pcLen,
734 int *piFormat)
735{
736 RT_NOREF(piFormat);
737
738 PSHCLX11CTX pCtx = reinterpret_cast<SHCLX11CTX *>(pClient);
739
740 LogFlowFunc(("pValue=%p, *pcLen=%u, *atomType=%d%s\n",
741 pValue, *pcLen, *atomType, *atomType == XT_CONVERT_FAIL ? " (XT_CONVERT_FAIL)" : ""));
742
743 Atom *pAtoms = (Atom *)pValue;
744
745 unsigned cFormats = *pcLen;
746
747 LogRel2(("Shared Clipboard: Querying X11 formats ...\n"));
748 LogRel2(("Shared Clipboard: %u X11 formats were found\n", cFormats));
749
750 SHCLX11FMTIDX *paIdxFmt = NULL;
751 if ( cFormats
752 && pValue
753 && (*atomType != XT_CONVERT_FAIL /* time out */))
754 {
755 /* Allocated array to hold the format indices. */
756 paIdxFmt = (SHCLX11FMTIDX *)RTMemAllocZ(cFormats * sizeof(SHCLX11FMTIDX));
757 }
758
759#if !defined(TESTCASE)
760 if (pValue)
761 {
762 for (unsigned i = 0; i < cFormats; ++i)
763 {
764 if (pAtoms[i])
765 {
766 char *pszName = XGetAtomName(XtDisplay(widget), pAtoms[i]);
767 LogRel2(("Shared Clipboard: Found X11 format '%s'\n", pszName));
768 XFree(pszName);
769 }
770 else
771 LogFunc(("Found empty target\n"));
772 }
773 }
774#endif
775
776 if (paIdxFmt)
777 {
778 for (unsigned i = 0; i < cFormats; ++i)
779 {
780 for (unsigned j = 0; j < RT_ELEMENTS(g_aFormats); ++j)
781 {
782 Atom target = XInternAtom(XtDisplay(widget),
783 g_aFormats[j].pcszAtom, False);
784 if (*(pAtoms + i) == target)
785 paIdxFmt[i] = j;
786 }
787#if !defined(TESTCASE)
788 if (paIdxFmt[i] != SHCLX11FMT_INVALID)
789 LogRel2(("Shared Clipboard: Reporting X11 format '%s'\n", g_aFormats[paIdxFmt[i]].pcszAtom));
790#endif
791 }
792 }
793 else
794 LogFunc(("Reporting empty targets (none reported or allocation failure)\n"));
795
796 clipUpdateX11Targets(pCtx, paIdxFmt, cFormats);
797 RTMemFree(paIdxFmt);
798
799 XtFree(reinterpret_cast<char *>(pValue));
800}
801
802/**
803 * Queries the current formats ("targets") of the X11 clipboard ("CLIPBOARD").
804 *
805 * @param pCtx The X11 clipboard context to use.
806 */
807SHCL_X11_DECL(void) clipQueryX11Targets(PSHCLX11CTX pCtx)
808{
809#ifndef TESTCASE
810
811# ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
812 if (clipGetXtBusy(pCtx))
813 {
814 clipSetXtNeedsUpdate(pCtx, true);
815 return;
816 }
817 clipSetXtBusy(pCtx, true);
818# endif
819
820 XtGetSelectionValue(pCtx->pWidget,
821 clipGetAtom(pCtx, "CLIPBOARD"),
822 clipGetAtom(pCtx, "TARGETS"),
823 clipQueryX11TargetsCallback, pCtx,
824 CurrentTime);
825#else
826 tstRequestTargets(pCtx);
827#endif
828}
829
830typedef struct
831{
832 int type; /* event base */
833 unsigned long serial;
834 Bool send_event;
835 Display *display;
836 Window window;
837 int subtype;
838 Window owner;
839 Atom selection;
840 Time timestamp;
841 Time selection_timestamp;
842} XFixesSelectionNotifyEvent;
843
844#ifndef TESTCASE
845/**
846 * Waits until an event arrives and handle it if it is an XFIXES selection
847 * event, which Xt doesn't know about.
848 *
849 * @param pCtx The X11 clipboard context to use.
850 */
851static void clipPeekEventAndDoXFixesHandling(PSHCLX11CTX pCtx)
852{
853 union
854 {
855 XEvent event;
856 XFixesSelectionNotifyEvent fixes;
857 } event = { { 0 } };
858
859 if (XtAppPeekEvent(pCtx->pAppContext, &event.event))
860 {
861 if ( (event.event.type == pCtx->fixesEventBase)
862 && (event.fixes.owner != XtWindow(pCtx->pWidget)))
863 {
864 if ( (event.fixes.subtype == 0 /* XFixesSetSelectionOwnerNotify */)
865 && (event.fixes.owner != 0))
866 clipQueryX11Targets(pCtx);
867 else
868 clipReportEmpty(pCtx);
869 }
870 }
871}
872
873/**
874 * The main loop of our X11 event thread.
875 *
876 * @returns VBox status code.
877 * @param hThreadSelf Associated thread handle.
878 * @param pvUser Pointer to the X11 clipboard context to use.
879 */
880static DECLCALLBACK(int) clipThreadMain(RTTHREAD hThreadSelf, void *pvUser)
881{
882 PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUser;
883 AssertPtr(pCtx);
884
885 LogFlowFunc(("pCtx=%p\n", pCtx));
886
887 bool fSignalled = false; /* Whether we have signalled the parent already or not. */
888
889 int rc = clipInitInternal(pCtx);
890 if (RT_SUCCESS(rc))
891 {
892 rc = clipRegisterContext(pCtx);
893 if (RT_SUCCESS(rc))
894 {
895 if (pCtx->fGrabClipboardOnStart)
896 clipQueryX11Targets(pCtx);
897
898 pCtx->fThreadStarted = true;
899
900 /* We're now ready to run, tell parent. */
901 int rc2 = RTThreadUserSignal(hThreadSelf);
902 AssertRC(rc2);
903
904 fSignalled = true;
905
906 while (XtAppGetExitFlag(pCtx->pAppContext) == FALSE)
907 {
908 clipPeekEventAndDoXFixesHandling(pCtx);
909 XtAppProcessEvent(pCtx->pAppContext, XtIMAll);
910 }
911
912 LogRel(("Shared Clipboard: X11 event thread exiting\n"));
913
914 clipUnregisterContext(pCtx);
915 }
916 else
917 {
918 LogRel(("Shared Clipboard: unable to register clip context: %Rrc\n", rc));
919 }
920
921 clipUninitInternal(pCtx);
922 }
923
924 if (!fSignalled) /* Signal parent if we didn't do so yet. */
925 {
926 int rc2 = RTThreadUserSignal(hThreadSelf);
927 AssertRC(rc2);
928 }
929
930 LogFlowFuncLeaveRC(rc);
931 return rc;
932}
933
934/**
935 * Worker function for stopping the clipboard which runs on the event
936 * thread.
937 *
938 * @param pvUserData Pointer to the X11 clipboard context to use.
939 */
940static void clipThreadSignalStop(void *pvUserData, void *)
941{
942 PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUserData;
943
944 /* This might mean that we are getting stopped twice. */
945 Assert(pCtx->pWidget != NULL);
946
947 /* Set the termination flag to tell the Xt event loop to exit. We
948 * reiterate that any outstanding requests from the X11 event loop to
949 * the VBox part *must* have returned before we do this. */
950 XtAppSetExitFlag(pCtx->pAppContext);
951}
952
953/**
954 * Sets up the XFixes library and load the XFixesSelectSelectionInput symbol.
955 */
956static int clipLoadXFixes(Display *pDisplay, PSHCLX11CTX pCtx)
957{
958 int rc;
959
960 void *hFixesLib = dlopen("libXfixes.so.1", RTLD_LAZY);
961 if (!hFixesLib)
962 hFixesLib = dlopen("libXfixes.so.2", RTLD_LAZY);
963 if (!hFixesLib)
964 hFixesLib = dlopen("libXfixes.so.3", RTLD_LAZY);
965 if (!hFixesLib)
966 hFixesLib = dlopen("libXfixes.so.4", RTLD_LAZY);
967 if (hFixesLib)
968 {
969 /* For us, a NULL function pointer is a failure */
970 pCtx->fixesSelectInput = (void (*)(Display *, Window, Atom, long unsigned int))
971 (uintptr_t)dlsym(hFixesLib, "XFixesSelectSelectionInput");
972 if (pCtx->fixesSelectInput)
973 {
974 int dummy1 = 0;
975 int dummy2 = 0;
976 if (XQueryExtension(pDisplay, "XFIXES", &dummy1, &pCtx->fixesEventBase, &dummy2) != 0)
977 {
978 if (pCtx->fixesEventBase >= 0)
979 {
980 rc = VINF_SUCCESS;
981 }
982 else
983 {
984 LogRel(("Shared Clipboard: fixesEventBase is less than zero: %d\n", pCtx->fixesEventBase));
985 rc = VERR_NOT_SUPPORTED;
986 }
987 }
988 else
989 {
990 LogRel(("Shared Clipboard: XQueryExtension failed\n"));
991 rc = VERR_NOT_SUPPORTED;
992 }
993 }
994 else
995 {
996 LogRel(("Shared Clipboard: Symbol XFixesSelectSelectionInput not found!\n"));
997 rc = VERR_NOT_SUPPORTED;
998 }
999 }
1000 else
1001 {
1002 LogRel(("Shared Clipboard: libxFixes.so.* not found!\n"));
1003 rc = VERR_NOT_SUPPORTED;
1004 }
1005 return rc;
1006}
1007
1008/**
1009 * This is the callback which is scheduled when data is available on the
1010 * wakeup pipe. It simply reads all data from the pipe.
1011 *
1012 * @param pvUserData Pointer to the X11 clipboard context to use.
1013 */
1014static void clipThreadDrainWakeupPipe(XtPointer pvUserData, int *, XtInputId *)
1015{
1016 LogFlowFuncEnter();
1017
1018 PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUserData;
1019 char acBuf[WAKE_UP_STRING_LEN];
1020
1021 while (read(pCtx->wakeupPipeRead, acBuf, sizeof(acBuf)) > 0) {}
1022}
1023#endif /* !TESTCASE */
1024
1025/**
1026 * X11-specific initialisation for the Shared Clipboard.
1027 *
1028 * Note: Must be called from the thread serving the Xt stuff.
1029 *
1030 * @returns VBox status code.
1031 * @param pCtx The X11 clipboard context to init.
1032 */
1033static int clipInitInternal(PSHCLX11CTX pCtx)
1034{
1035 LogFlowFunc(("pCtx=%p\n", pCtx));
1036
1037 /* Make sure we are thread safe. */
1038 XtToolkitThreadInitialize();
1039
1040 /*
1041 * Set up the Clipboard application context and main window. We call all
1042 * these functions directly instead of calling XtOpenApplication() so
1043 * that we can fail gracefully if we can't get an X11 display.
1044 */
1045 XtToolkitInitialize();
1046
1047 int rc = VINF_SUCCESS;
1048
1049 Assert(pCtx->pAppContext == NULL); /* No nested initialization. */
1050 pCtx->pAppContext = XtCreateApplicationContext();
1051 if (pCtx->pAppContext == NULL)
1052 {
1053 LogRel(("Shared Clipboard: Failed to create Xt application context\n"));
1054 return VERR_NOT_SUPPORTED; /** @todo Fudge! */
1055 }
1056
1057 /* Create a window and make it a clipboard viewer. */
1058 int cArgc = 0;
1059 char *pcArgv = 0;
1060 Display *pDisplay = XtOpenDisplay(pCtx->pAppContext, 0, 0, "VBoxShCl", 0, 0, &cArgc, &pcArgv);
1061 if (pDisplay == NULL)
1062 {
1063 LogRel(("Shared Clipboard: Failed to connect to the X11 clipboard - the window system may not be running\n"));
1064 rc = VERR_NOT_SUPPORTED;
1065 }
1066
1067#ifndef TESTCASE
1068 if (RT_SUCCESS(rc))
1069 {
1070 rc = clipLoadXFixes(pDisplay, pCtx);
1071 if (RT_FAILURE(rc))
1072 LogRel(("Shared Clipboard: Failed to load the XFIXES extension\n"));
1073 }
1074#endif
1075
1076 if (RT_SUCCESS(rc))
1077 {
1078 pCtx->pWidget = XtVaAppCreateShell(0, "VBoxShCl",
1079 applicationShellWidgetClass,
1080 pDisplay,
1081 XtNwidth, 1, XtNheight, 1,
1082 NULL);
1083 if (pCtx->pWidget == NULL)
1084 {
1085 LogRel(("Shared Clipboard: Failed to create Xt app shell\n"));
1086 rc = VERR_NO_MEMORY; /** @todo r=andy Improve this. */
1087 }
1088 else
1089 {
1090#ifndef TESTCASE
1091 if (!XtAppAddInput(pCtx->pAppContext, pCtx->wakeupPipeRead,
1092 (XtPointer) XtInputReadMask,
1093 clipThreadDrainWakeupPipe, (XtPointer) pCtx))
1094 {
1095 LogRel(("Shared Clipboard: Failed to add input to Xt app context\n"));
1096 rc = VERR_ACCESS_DENIED; /** @todo r=andy Improve this. */
1097 }
1098#endif
1099 }
1100 }
1101
1102 if (RT_SUCCESS(rc))
1103 {
1104 XtSetMappedWhenManaged(pCtx->pWidget, false);
1105 XtRealizeWidget(pCtx->pWidget);
1106
1107#ifndef TESTCASE
1108 /* Enable clipboard update notification. */
1109 pCtx->fixesSelectInput(pDisplay, XtWindow(pCtx->pWidget),
1110 clipGetAtom(pCtx, "CLIPBOARD"),
1111 7 /* All XFixes*Selection*NotifyMask flags */);
1112#endif
1113 }
1114
1115 if (RT_FAILURE(rc))
1116 {
1117 LogRel(("Shared Clipboard: Initialisation failed: %Rrc\n", rc));
1118 clipUninitInternal(pCtx);
1119 }
1120
1121 LogFlowFuncLeaveRC(rc);
1122 return rc;
1123}
1124
1125/**
1126 * X11-specific uninitialisation for the Shared Clipboard.
1127 *
1128 * Note: Must be called from the thread serving the Xt stuff.
1129 *
1130 * @param pCtx The X11 clipboard context to uninit.
1131 */
1132static void clipUninitInternal(PSHCLX11CTX pCtx)
1133{
1134 AssertPtrReturnVoid(pCtx);
1135
1136 LogFlowFunc(("pCtx=%p\n", pCtx));
1137
1138 if (pCtx->pWidget)
1139 {
1140 /* Valid widget + invalid appcontext = bug. But don't return yet. */
1141 AssertPtr(pCtx->pAppContext);
1142
1143 XtDestroyWidget(pCtx->pWidget);
1144 pCtx->pWidget = NULL;
1145 }
1146
1147 if (pCtx->pAppContext)
1148 {
1149 XtDestroyApplicationContext(pCtx->pAppContext);
1150 pCtx->pAppContext = NULL;
1151 }
1152
1153 LogFlowFuncLeaveRC(VINF_SUCCESS);
1154}
1155
1156/**
1157 * Sets the callback table, internal version.
1158 *
1159 * @param pCtx The clipboard context.
1160 * @param pCallbacks Callback table to set. If NULL, the current callback table will be cleared.
1161 */
1162static void shClX11SetCallbacksInternal(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks)
1163{
1164 if (pCallbacks)
1165 {
1166 memcpy(&pCtx->Callbacks, pCallbacks, sizeof(SHCLCALLBACKS));
1167 }
1168 else
1169 RT_ZERO(pCtx->Callbacks);
1170}
1171
1172/**
1173 * Sets the callback table.
1174 *
1175 * @param pCtx The clipboard context.
1176 * @param pCallbacks Callback table to set. If NULL, the current callback table will be cleared.
1177 */
1178void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks)
1179{
1180 shClX11SetCallbacksInternal(pCtx, pCallbacks);
1181}
1182
1183/**
1184 * Initializes a X11 context of the Shared Clipboard.
1185 *
1186 * @returns VBox status code.
1187 * @param pCtx The clipboard context to initialize.
1188 * @param pCallbacks Callback table to use.
1189 * @param pParent Parent context to use.
1190 * @param fHeadless Whether the code runs in a headless environment or not.
1191 */
1192int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent, bool fHeadless)
1193{
1194 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1195
1196 LogFlowFunc(("pCtx=%p\n", pCtx));
1197
1198 int rc = VINF_SUCCESS;
1199
1200 RT_BZERO(pCtx, sizeof(SHCLX11CTX));
1201
1202 if (fHeadless)
1203 {
1204 /*
1205 * If we don't find the DISPLAY environment variable we assume that
1206 * we are not connected to an X11 server. Don't actually try to do
1207 * this then, just fail silently and report success on every call.
1208 * This is important for VBoxHeadless.
1209 */
1210 LogRel(("Shared Clipboard: X11 DISPLAY variable not set -- disabling clipboard sharing\n"));
1211 }
1212
1213 /* Init clipboard cache. */
1214 ShClCacheInit(&pCtx->Cache);
1215
1216 /* Install given callbacks. */
1217 shClX11SetCallbacksInternal(pCtx, pCallbacks);
1218
1219 pCtx->fHaveX11 = !fHeadless;
1220 pCtx->pFrontend = pParent;
1221
1222#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
1223 pCtx->fXtBusy = false;
1224 pCtx->fXtNeedsUpdate = false;
1225#endif
1226
1227#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
1228 ShClTransferHttpServerInit(&pCtx->HttpCtx.HttpServer);
1229#endif
1230
1231#ifdef TESTCASE
1232 if (RT_SUCCESS(rc))
1233 {
1234 /** @todo The testcases currently do not utilize the threading code. So init stuff here. */
1235 rc = clipInitInternal(pCtx);
1236 if (RT_SUCCESS(rc))
1237 rc = clipRegisterContext(pCtx);
1238 }
1239#endif
1240
1241 LogFlowFuncLeaveRC(rc);
1242 return rc;
1243}
1244
1245/**
1246 * Destroys a Shared Clipboard X11 context.
1247 *
1248 * @param pCtx The X11 clipboard context to destroy.
1249 */
1250void ShClX11Destroy(PSHCLX11CTX pCtx)
1251{
1252 if (!pCtx)
1253 return;
1254
1255 LogFlowFunc(("pCtx=%p\n", pCtx));
1256
1257 /* Destroy clipboard cache. */
1258 ShClCacheDestroy(&pCtx->Cache);
1259
1260#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
1261 ShClTransferHttpServerDestroy(&pCtx->HttpCtx.HttpServer);
1262#endif
1263
1264#ifdef TESTCASE
1265 /** @todo The testcases currently do not utilize the threading code. So uninit stuff here. */
1266 clipUnregisterContext(pCtx);
1267 clipUninitInternal(pCtx);
1268#endif
1269
1270 if (pCtx->fHaveX11)
1271 {
1272 /* We set this to NULL when the event thread exits. It really should
1273 * have exited at this point, when we are about to unload the code from
1274 * memory. */
1275 Assert(pCtx->pWidget == NULL);
1276 }
1277}
1278
1279#ifndef TESTCASE
1280/**
1281 * Starts our own Xt even thread for handling Shared Clipboard messages, extended version.
1282 *
1283 * @returns VBox status code.
1284 * @param pCtx The X11 clipboard context to use.
1285 * @param pszName Thread name to use.
1286 * @param fGrab Whether we should try to grab the shared clipboard at once.
1287 */
1288int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab)
1289{
1290 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1291
1292 /*
1293 * Immediately return if we are not connected to the X server.
1294 */
1295 if (!pCtx->fHaveX11)
1296 return VINF_SUCCESS;
1297
1298 pCtx->fGrabClipboardOnStart = fGrab;
1299
1300 clipResetX11Formats(pCtx);
1301
1302 int rc;
1303
1304 /*
1305 * Create the pipes.
1306 ** @todo r=andy Replace this with RTPipe API.
1307 */
1308 int pipes[2];
1309 if (!pipe(pipes))
1310 {
1311 pCtx->wakeupPipeRead = pipes[0];
1312 pCtx->wakeupPipeWrite = pipes[1];
1313
1314 if (!fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK))
1315 {
1316 rc = VINF_SUCCESS;
1317 }
1318 else
1319 rc = RTErrConvertFromErrno(errno);
1320 }
1321 else
1322 rc = RTErrConvertFromErrno(errno);
1323
1324 if (RT_SUCCESS(rc))
1325 {
1326 LogRel2(("Shared Clipboard: Starting X11 event thread ...\n"));
1327
1328 rc = RTThreadCreate(&pCtx->Thread, clipThreadMain, pCtx, 0,
1329 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, pszName);
1330 if (RT_SUCCESS(rc))
1331 rc = RTThreadUserWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */);
1332
1333 if (RT_FAILURE(rc))
1334 {
1335 LogRel(("Shared Clipboard: Failed to start the X11 event thread with %Rrc\n", rc));
1336 clipUninitInternal(pCtx);
1337 }
1338 else
1339 {
1340 if (!pCtx->fThreadStarted)
1341 {
1342 LogRel(("Shared Clipboard: X11 event thread reported an error while starting\n"));
1343 }
1344 else
1345 LogRel2(("Shared Clipboard: X11 event thread started\n"));
1346 }
1347 }
1348
1349 LogFlowFuncLeaveRC(rc);
1350 return rc;
1351}
1352
1353/**
1354 * Starts our own Xt even thread for handling Shared Clipboard messages.
1355 *
1356 * @returns VBox status code.
1357 * @param pCtx The X11 clipboard context to use.
1358 * @param fGrab Whether we should try to grab the shared clipboard at once.
1359 */
1360int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool fGrab)
1361{
1362 return ShClX11ThreadStartEx(pCtx, "SHCLX11", fGrab);
1363}
1364
1365/**
1366 * Stops the Shared Clipboard Xt even thread.
1367 *
1368 * @note Any requests from this object to get clipboard data from VBox
1369 * *must* have completed or aborted before we are called, as
1370 * otherwise the X11 event loop will still be waiting for the request
1371 * to return and will not be able to terminate.
1372 *
1373 * @returns VBox status code.
1374 * @param pCtx The X11 clipboard context to use.
1375 */
1376int ShClX11ThreadStop(PSHCLX11CTX pCtx)
1377{
1378 int rc;
1379 /*
1380 * Immediately return if we are not connected to the X server.
1381 */
1382 if (!pCtx->fHaveX11)
1383 return VINF_SUCCESS;
1384
1385 LogRel2(("Shared Clipboard: Signalling the X11 event thread to stop\n"));
1386
1387 /* Write to the "stop" pipe. */
1388 rc = clipThreadScheduleCall(pCtx, clipThreadSignalStop, (XtPointer)pCtx);
1389 if (RT_FAILURE(rc))
1390 {
1391 LogRel(("Shared Clipboard: cannot notify X11 event thread on shutdown with %Rrc\n", rc));
1392 return rc;
1393 }
1394
1395 LogRel2(("Shared Clipboard: Waiting for X11 event thread to stop ...\n"));
1396
1397 int rcThread;
1398 rc = RTThreadWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */, &rcThread);
1399 if (RT_SUCCESS(rc))
1400 rc = rcThread;
1401 if (RT_SUCCESS(rc))
1402 {
1403 if (pCtx->wakeupPipeRead != 0)
1404 {
1405 close(pCtx->wakeupPipeRead);
1406 pCtx->wakeupPipeRead = 0;
1407 }
1408
1409 if (pCtx->wakeupPipeWrite != 0)
1410 {
1411 close(pCtx->wakeupPipeWrite);
1412 pCtx->wakeupPipeWrite = 0;
1413 }
1414 }
1415
1416 if (RT_SUCCESS(rc))
1417 {
1418 LogRel2(("Shared Clipboard: X11 event thread stopped successfully\n"));
1419 }
1420 else
1421 LogRel(("Shared Clipboard: Stopping X11 event thread failed with %Rrc\n", rc));
1422
1423 LogFlowFuncLeaveRC(rc);
1424 return rc;
1425}
1426#endif /* !TESTCASE */
1427
1428/**
1429 * Returns the targets supported by VBox.
1430 *
1431 * This will return a list of atoms which tells the caller
1432 * what kind of clipboard formats we support.
1433 *
1434 * @returns VBox status code.
1435 * @param pCtx The X11 clipboard context to use.
1436 * @param atomTypeReturn The type of the data we are returning.
1437 * @param pValReturn A pointer to the data we are returning. This
1438 * should be set to memory allocated by XtMalloc,
1439 * which will be freed later by the Xt toolkit.
1440 * @param pcLenReturn The length of the data we are returning.
1441 * @param piFormatReturn The format (8bit, 16bit, 32bit) of the data we are
1442 * returning.
1443 * @note X11 backend code, called by the XtOwnSelection callback.
1444 */
1445static int clipCreateX11Targets(PSHCLX11CTX pCtx, Atom *atomTypeReturn,
1446 XtPointer *pValReturn,
1447 unsigned long *pcLenReturn,
1448 int *piFormatReturn)
1449{
1450 const unsigned cFixedTargets = 3; /* See below. */
1451
1452 Atom *pAtomTargets = (Atom *)XtMalloc((SHCL_MAX_X11_FORMATS + cFixedTargets) * sizeof(Atom));
1453 if (!pAtomTargets)
1454 return VERR_NO_MEMORY;
1455
1456 unsigned cTargets = 0;
1457 SHCLX11FMTIDX idxFmt = NIL_CLIPX11FORMAT;
1458 do
1459 {
1460 idxFmt = clipEnumX11Formats(pCtx->vboxFormats, idxFmt);
1461 if (idxFmt != NIL_CLIPX11FORMAT)
1462 {
1463 pAtomTargets[cTargets] = clipAtomForX11Format(pCtx, idxFmt);
1464 ++cTargets;
1465 }
1466 } while (idxFmt != NIL_CLIPX11FORMAT);
1467
1468 /* We always offer these fixed targets. */
1469 pAtomTargets[cTargets] = clipGetAtom(pCtx, "TARGETS");
1470 pAtomTargets[cTargets + 1] = clipGetAtom(pCtx, "MULTIPLE");
1471 pAtomTargets[cTargets + 2] = clipGetAtom(pCtx, "TIMESTAMP");
1472
1473 *atomTypeReturn = XA_ATOM;
1474 *pValReturn = (XtPointer)pAtomTargets;
1475 *pcLenReturn = cTargets + cFixedTargets;
1476 *piFormatReturn = 32;
1477
1478 LogFlowFunc(("cTargets=%u\n", cTargets + cFixedTargets));
1479
1480 return VINF_SUCCESS;
1481}
1482
1483/**
1484 * Helper for ShClX11RequestDataForX11Callback() that will cache the data returned.
1485 *
1486 * @returns VBox status code. VERR_NO_DATA if no data available.
1487 * @param pCtx The X11 clipboard context to use.
1488 * @param uFmt Clipboard format to read data in.
1489 * @param ppv Returns an allocated buffer with data read on success.
1490 * Needs to be free'd with RTMemFree() by the caller.
1491 * @param pcb Returns the amount of data read (in bytes) on success.
1492 *
1493 * @thread X11 event thread.
1494 */
1495static int shClX11RequestDataForX11CallbackHelper(PSHCLX11CTX pCtx, SHCLFORMAT uFmt,
1496 void **ppv, uint32_t *pcb)
1497{
1498 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1499 AssertPtrReturn(ppv, VERR_INVALID_POINTER);
1500 AssertPtrReturn(pcb, VERR_INVALID_POINTER);
1501
1502#ifdef LOG_ENABLED
1503 char *pszFmts = ShClFormatsToStrA(uFmt);
1504 AssertPtrReturn(pszFmts, VERR_NO_MEMORY);
1505 LogRel2(("Shared Clipboard: Requesting data for X11 from source as '%s'\n", pszFmts));
1506 RTStrFree(pszFmts);
1507#endif
1508
1509 int rc = VINF_SUCCESS;
1510
1511 void *pv = NULL;
1512 uint32_t cb = 0;
1513
1514 PSHCLCACHEENTRY pCacheEntry = ShClCacheGet(&pCtx->Cache, uFmt);
1515 if (!pCacheEntry) /* Cache miss */
1516 {
1517 AssertPtrReturn(pCtx->Callbacks.pfnOnRequestDataFromSource, VERR_INVALID_POINTER);
1518 rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pv, &cb,
1519 NULL /* pvUser */);
1520 if (RT_SUCCESS(rc))
1521 rc = ShClCacheSet(&pCtx->Cache, uFmt, pv, cb);
1522 }
1523 else /* Cache hit */
1524 {
1525 void *pvCache = NULL;
1526 size_t cbCache = 0;
1527 ShClCacheEntryGet(pCacheEntry, &pvCache, &cbCache);
1528 if ( pvCache
1529 && cbCache)
1530 {
1531 pv = RTMemDup(pvCache, cbCache);
1532 if (pv)
1533 {
1534 cb = cbCache;
1535 }
1536 else
1537 rc = VERR_NO_MEMORY;
1538 }
1539 }
1540
1541 LogFlowFunc(("pCtx=%p, uFmt=%#x -> Cache %s\n", pCtx, uFmt, pCacheEntry ? "HIT" : "MISS"));
1542
1543 /* Safey net in case the stuff above misbehaves
1544 * (must return VERR_NO_DATA if no data available). */
1545 if ( RT_SUCCESS(rc)
1546 && (pv == NULL || cb == 0))
1547 rc = VERR_NO_DATA;
1548
1549 if (RT_SUCCESS(rc))
1550 {
1551 *ppv = pv;
1552 *pcb = cb;
1553 }
1554
1555 if (RT_FAILURE(rc))
1556 LogRel(("Shared Clipboard: Requesting data for X11 from source failed with %Rrc\n", rc));
1557
1558 LogFlowFunc(("Returning pv=%p, cb=%RU32, rc=%Rrc\n", pv, cb, rc));
1559 return rc;
1560}
1561
1562/**
1563 * Satisfies a request from X11 to convert the clipboard text to UTF-8 LF.
1564 *
1565 * @returns VBox status code. VERR_NO_DATA if no data was converted.
1566 * @param pDisplay An X11 display structure, needed for conversions
1567 * performed by Xlib.
1568 * @param pv The text to be converted (UCS-2 with Windows EOLs).
1569 * @param cb The length of the text in @cb in bytes.
1570 * @param atomTypeReturn Where to store the atom for the type of the data
1571 * we are returning.
1572 * @param pValReturn Where to store the pointer to the data we are
1573 * returning. This should be to memory allocated by
1574 * XtMalloc, which will be freed by the Xt toolkit
1575 * later.
1576 * @param pcLenReturn Where to store the length of the data we are
1577 * returning.
1578 * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
1579 * data we are returning.
1580 */
1581static int clipConvertUtf16ToX11Data(Display *pDisplay, PRTUTF16 pwszSrc,
1582 size_t cbSrc, Atom *atomTarget,
1583 Atom *atomTypeReturn,
1584 XtPointer *pValReturn,
1585 unsigned long *pcLenReturn,
1586 int *piFormatReturn)
1587{
1588 RT_NOREF(pDisplay);
1589 AssertReturn(cbSrc % sizeof(RTUTF16) == 0, VERR_INVALID_PARAMETER);
1590
1591 const size_t cwcSrc = cbSrc / sizeof(RTUTF16);
1592 if (!cwcSrc)
1593 return VERR_NO_DATA;
1594
1595 /* This may slightly overestimate the space needed. */
1596 size_t chDst = 0;
1597 int rc = ShClUtf16LenUtf8(pwszSrc, cwcSrc, &chDst);
1598 if (RT_SUCCESS(rc))
1599 {
1600 chDst++; /* Add space for terminator. */
1601
1602 char *pszDst = (char *)XtMalloc(chDst);
1603 if (pszDst)
1604 {
1605 size_t cbActual = 0;
1606 rc = ShClConvUtf16CRLFToUtf8LF(pwszSrc, cwcSrc, pszDst, chDst, &cbActual);
1607 if (RT_SUCCESS(rc))
1608 {
1609 *atomTypeReturn = *atomTarget;
1610 *pValReturn = (XtPointer)pszDst;
1611 *pcLenReturn = cbActual + 1 /* Include terminator */;
1612 *piFormatReturn = 8;
1613 }
1614 }
1615 else
1616 rc = VERR_NO_MEMORY;
1617 }
1618
1619 LogFlowFuncLeaveRC(rc);
1620 return rc;
1621}
1622
1623/**
1624 * Satisfies a request from X11 to convert the clipboard HTML fragment to UTF-8. We
1625 * return null-terminated text, but can cope with non-null-terminated input.
1626 *
1627 * @returns VBox status code.
1628 * @param pDisplay An X11 display structure, needed for conversions
1629 * performed by Xlib.
1630 * @param pv The text to be converted (UTF8 with Windows EOLs).
1631 * @param cb The length of the text in @cb in bytes.
1632 * @param atomTypeReturn Where to store the atom for the type of the data
1633 * we are returning.
1634 * @param pValReturn Where to store the pointer to the data we are
1635 * returning. This should be to memory allocated by
1636 * XtMalloc, which will be freed by the Xt toolkit later.
1637 * @param pcLenReturn Where to store the length of the data we are returning.
1638 * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
1639 * data we are returning.
1640 */
1641static int clipConvertHtmlToX11Data(Display *pDisplay, const char *pszSrc,
1642 size_t cbSrc, Atom *atomTarget,
1643 Atom *atomTypeReturn,
1644 XtPointer *pValReturn,
1645 unsigned long *pcLenReturn,
1646 int *piFormatReturn)
1647{
1648 RT_NOREF(pDisplay, pValReturn);
1649
1650 /* This may slightly overestimate the space needed. */
1651 LogFlowFunc(("Source: %s", pszSrc));
1652
1653 char *pszDest = (char *)XtMalloc(cbSrc);
1654 if (pszDest == NULL)
1655 return VERR_NO_MEMORY;
1656
1657 memcpy(pszDest, pszSrc, cbSrc);
1658
1659 *atomTypeReturn = *atomTarget;
1660 *pValReturn = (XtPointer)pszDest;
1661 *pcLenReturn = cbSrc;
1662 *piFormatReturn = 8;
1663
1664 return VINF_SUCCESS;
1665}
1666
1667
1668/**
1669 * Does this atom correspond to one of the two selection types we support?
1670 *
1671 * @param pCtx The X11 clipboard context to use.
1672 * @param selType The atom in question.
1673 */
1674static bool clipIsSupportedSelectionType(PSHCLX11CTX pCtx, Atom selType)
1675{
1676 return( (selType == clipGetAtom(pCtx, "CLIPBOARD"))
1677 || (selType == clipGetAtom(pCtx, "PRIMARY")));
1678}
1679
1680/**
1681 * Removes a trailing nul character from a string by adjusting the string
1682 * length. Some X11 applications don't like zero-terminated text...
1683 *
1684 * @param pText The text in question.
1685 * @param pcText The length of the text, adjusted on return.
1686 * @param format The format of the text.
1687 */
1688static void clipTrimTrailingNul(XtPointer pText, unsigned long *pcText,
1689 SHCLX11FMT format)
1690{
1691 AssertPtrReturnVoid(pText);
1692 AssertPtrReturnVoid(pcText);
1693 AssertReturnVoid((format == SHCLX11FMT_UTF8) || (format == SHCLX11FMT_TEXT) || (format == SHCLX11FMT_HTML));
1694
1695 if (((char *)pText)[*pcText - 1] == '\0')
1696 --(*pcText);
1697}
1698
1699static int clipConvertToX11Data(PSHCLX11CTX pCtx, Atom *atomTarget,
1700 Atom *atomTypeReturn,
1701 XtPointer *pValReturn,
1702 unsigned long *pcLenReturn,
1703 int *piFormatReturn)
1704{
1705 int rc = VERR_NOT_SUPPORTED; /* Play safe by default. */
1706
1707 SHCLX11FMTIDX idxFmtX11 = clipFindX11FormatByAtom(pCtx, *atomTarget);
1708 SHCLX11FMT fmtX11 = clipRealFormatForX11Format(idxFmtX11);
1709
1710 LogFlowFunc(("vboxFormats=0x%x, idxFmtX11=%u ('%s'), fmtX11=%u\n",
1711 pCtx->vboxFormats, idxFmtX11, g_aFormats[idxFmtX11].pcszAtom, fmtX11));
1712
1713 char *pszFmts = ShClFormatsToStrA(pCtx->vboxFormats);
1714 AssertPtrReturn(pszFmts, VERR_NO_MEMORY);
1715 LogRel2(("Shared Clipboard: Converting VBox formats '%s' to '%s' for X11\n",
1716 pszFmts, fmtX11 == SHCLX11FMT_INVALID ? "<invalid>" : g_aFormats[idxFmtX11].pcszAtom));
1717 RTStrFree(pszFmts);
1718
1719 void *pv = NULL;
1720 uint32_t cb = 0;
1721
1722 if ( ( (fmtX11 == SHCLX11FMT_UTF8)
1723 || (fmtX11 == SHCLX11FMT_TEXT)
1724 )
1725 && (pCtx->vboxFormats & VBOX_SHCL_FMT_UNICODETEXT))
1726 {
1727 rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_UNICODETEXT, &pv, &cb);
1728 if ( RT_SUCCESS(rc)
1729 && ( (fmtX11 == SHCLX11FMT_UTF8)
1730 || (fmtX11 == SHCLX11FMT_TEXT)))
1731 {
1732 rc = clipConvertUtf16ToX11Data(XtDisplay(pCtx->pWidget),
1733 (PRTUTF16)pv, cb, atomTarget,
1734 atomTypeReturn, pValReturn,
1735 pcLenReturn, piFormatReturn);
1736 }
1737
1738 if (RT_SUCCESS(rc))
1739 clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, fmtX11);
1740
1741 RTMemFree(pv);
1742 }
1743 else if ( (fmtX11 == SHCLX11FMT_BMP)
1744 && (pCtx->vboxFormats & VBOX_SHCL_FMT_BITMAP))
1745 {
1746 rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_BITMAP, &pv, &cb);
1747 if ( RT_SUCCESS(rc)
1748 && (fmtX11 == SHCLX11FMT_BMP))
1749 {
1750 /* Create a full BMP from it. */
1751 rc = ShClDibToBmp(pv, cb, (void **)pValReturn,
1752 (size_t *)pcLenReturn);
1753 }
1754
1755 if (RT_SUCCESS(rc))
1756 {
1757 *atomTypeReturn = *atomTarget;
1758 *piFormatReturn = 8;
1759 }
1760
1761 RTMemFree(pv);
1762 }
1763 else if ( (fmtX11 == SHCLX11FMT_HTML)
1764 && (pCtx->vboxFormats & VBOX_SHCL_FMT_HTML))
1765 {
1766 rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_HTML, &pv, &cb);
1767 if (RT_SUCCESS(rc))
1768 {
1769 /**
1770 * The common VBox HTML encoding will be UTF-8.
1771 * Before sending it to the X11 clipboard we have to convert it to UTF-8 first.
1772 *
1773 * Strange that we get UTF-16 from the X11 clipboard, but
1774 * in same time we send UTF-8 to X11 clipboard and it works.
1775 ** @todo r=andy Verify this.
1776 */
1777 rc = clipConvertHtmlToX11Data(XtDisplay(pCtx->pWidget),
1778 (const char*)pv, cb, atomTarget,
1779 atomTypeReturn, pValReturn,
1780 pcLenReturn, piFormatReturn);
1781 if (RT_SUCCESS(rc))
1782 clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, fmtX11);
1783
1784 RTMemFree(pv);
1785 }
1786 }
1787#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1788 else if ( fmtX11 == SHCLX11FMT_URI_LIST
1789 || fmtX11 == SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES
1790 /** @todo BUGBUG Not sure about the following ones; test those. */
1791 || fmtX11 == SHCLX11FMT_URI_LIST_MATE_COPIED_FILES
1792 || fmtX11 == SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD
1793 || fmtX11 == SHCLX11FMT_URI_LIST_KDE_CUTSELECTION)
1794 {
1795 if (pCtx->vboxFormats & VBOX_SHCL_FMT_URI_LIST)
1796 {
1797 rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_URI_LIST, &pv, &cb);
1798 if (RT_SUCCESS(rc))
1799 {
1800 if ( fmtX11 == SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES
1801 /** @todo BUGBUG Ditto, see above. */
1802 || fmtX11 == SHCLX11FMT_URI_LIST_MATE_COPIED_FILES
1803 || fmtX11 == SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD
1804 || fmtX11 == SHCLX11FMT_URI_LIST_KDE_CUTSELECTION)
1805 {
1806 /* Note: There must be *no* final new line ('\n') at the end, otherwise Nautilus will crash! */
1807 char *pszData = NULL;
1808
1809 RTStrAPrintf(&pszData, "copy\n%s", (const char *)pv);
1810
1811 cb = strlen(pszData);
1812 pv = pszData;
1813 }
1814 else /* SHCLX11FMT_URI_LIST -> String only, w/o any special formatting. */
1815 {
1816 char *pszData = NULL;
1817 RTStrAPrintf(&pszData, "%s\n", (const char *)pv);
1818
1819 cb = strlen(pszData) + 1;
1820 pv = pszData;
1821 }
1822
1823# ifdef DEBUG_andy
1824 LogFlowFunc(("Data:\n%.*RhXd\n", cb, pv));
1825# endif
1826 void *pvDst = (void *)XtMalloc(cb);
1827 if (pvDst)
1828 {
1829 memcpy(pvDst, pv, cb);
1830
1831 *atomTypeReturn = *atomTarget;
1832 *pValReturn = (XtPointer)pvDst;
1833 *pcLenReturn = cb;
1834 *piFormatReturn = 8;
1835 }
1836 else
1837 rc = VERR_NO_MEMORY;
1838 }
1839
1840 RTMemFree(pv);
1841 pv = NULL;
1842 }
1843 /* else not supported yet. */
1844 }
1845#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
1846 else
1847 {
1848 *atomTypeReturn = XT_CONVERT_FAIL;
1849 *pValReturn = (XtPointer)NULL;
1850 *pcLenReturn = 0;
1851 *piFormatReturn = 0;
1852 }
1853
1854 if (RT_FAILURE(rc))
1855 {
1856 char *pszFmts2 = ShClFormatsToStrA(pCtx->vboxFormats);
1857 char *pszAtomName = XGetAtomName(XtDisplay(pCtx->pWidget), *atomTarget);
1858
1859 LogRel(("Shared Clipboard: Converting VBox formats '%s' to '%s' for X11 (idxFmtX11=%u, fmtX11=%u, atomTarget='%s') failed, rc=%Rrc\n",
1860 pszFmts2 ? pszFmts2 : "unknown", g_aFormats[idxFmtX11].pcszAtom, idxFmtX11, fmtX11, pszAtomName ? pszAtomName : "unknown", rc));
1861
1862 if (pszFmts2)
1863 RTStrFree(pszFmts2);
1864 if (pszAtomName)
1865 XFree(pszAtomName);
1866 }
1867
1868 LogFlowFuncLeaveRC(rc);
1869 return rc;
1870}
1871
1872/**
1873 * Returns VBox's clipboard data for an X11 client.
1874 *
1875 * @note Callback for XtOwnSelection.
1876 */
1877static Boolean clipXtConvertSelectionProc(Widget widget, Atom *atomSelection,
1878 Atom *atomTarget,
1879 Atom *atomTypeReturn,
1880 XtPointer *pValReturn,
1881 unsigned long *pcLenReturn,
1882 int *piFormatReturn)
1883{
1884 LogFlowFuncEnter();
1885
1886 PSHCLX11CTX pCtx = clipLookupContext(widget);
1887 if (!pCtx)
1888 return False;
1889
1890 /* Is this the rigt selection (clipboard) we were asked for? */
1891 if (!clipIsSupportedSelectionType(pCtx, *atomSelection))
1892 return False;
1893
1894 int rc;
1895 if (*atomTarget == clipGetAtom(pCtx, "TARGETS"))
1896 rc = clipCreateX11Targets(pCtx, atomTypeReturn, pValReturn,
1897 pcLenReturn, piFormatReturn);
1898 else
1899 rc = clipConvertToX11Data(pCtx, atomTarget, atomTypeReturn,
1900 pValReturn, pcLenReturn, piFormatReturn);
1901
1902#if 0 /** @todo Disabled -- crashes when running with tstClipboardGH-X11. */
1903 XSelectionRequestEvent* pReq =
1904 XtGetSelectionRequest(widget, *atomSelection, (XtRequestId)NULL);
1905 LogFlowFunc(("returning pVBoxWnd=%#x, ownerWnd=%#x, reqWnd=%#x, %RTbool, rc=%Rrc\n",
1906 XtWindow(pCtx->pWidget), pReq->owner, pReq->requestor, RT_SUCCESS(rc), rc));
1907#endif
1908 return RT_SUCCESS(rc) ? True : False;
1909}
1910
1911static void clipXtConvertSelectionProcLose(Widget widget, Atom *atomSelection)
1912{
1913 RT_NOREF(widget, atomSelection);
1914 LogFlowFuncEnter();
1915}
1916
1917static void clipXtConvertSelectionProcDone(Widget widget, Atom *atomSelection, Atom *atomTarget)
1918{
1919 RT_NOREF(widget, atomSelection, atomTarget);
1920 LogFlowFuncEnter();
1921}
1922
1923/**
1924 * Invalidates the local clipboard cache.
1925 *
1926 * @param pCtx The X11 clipboard context to use.
1927 */
1928static void clipInvalidateClipboardCache(PSHCLX11CTX pCtx)
1929{
1930 LogFlowFuncEnter();
1931
1932 ShClCacheInvalidate(&pCtx->Cache);
1933}
1934
1935/**
1936 * Takes possession of the X11 clipboard (and middle-button selection).
1937 *
1938 * @param pCtx The X11 clipboard context to use.
1939 * @param uFormats Clipboard formats to set.
1940 */
1941static void clipGrabX11Clipboard(PSHCLX11CTX pCtx, SHCLFORMATS uFormats)
1942{
1943 LogFlowFuncEnter();
1944
1945 /** @ŧodo r=andy The docs say: "the value CurrentTime is not acceptable" here!? */
1946 if (XtOwnSelection(pCtx->pWidget, clipGetAtom(pCtx, "CLIPBOARD"),
1947 CurrentTime,
1948 clipXtConvertSelectionProc, clipXtConvertSelectionProcLose, clipXtConvertSelectionProcDone))
1949 {
1950 pCtx->vboxFormats = uFormats;
1951
1952 /* Grab the middle-button paste selection too. */
1953 XtOwnSelection(pCtx->pWidget, clipGetAtom(pCtx, "PRIMARY"),
1954 CurrentTime, clipXtConvertSelectionProc, NULL, 0);
1955#ifndef TESTCASE
1956 /* Xt suppresses these if we already own the clipboard, so send them
1957 * ourselves. */
1958 XSetSelectionOwner(XtDisplay(pCtx->pWidget),
1959 clipGetAtom(pCtx, "CLIPBOARD"),
1960 XtWindow(pCtx->pWidget), CurrentTime);
1961 XSetSelectionOwner(XtDisplay(pCtx->pWidget),
1962 clipGetAtom(pCtx, "PRIMARY"),
1963 XtWindow(pCtx->pWidget), CurrentTime);
1964#endif
1965 }
1966}
1967
1968/**
1969 * Worker function for ShClX11ReportFormatsToX11Async.
1970 *
1971 * @param pvUserData Pointer to a CLIPNEWVBOXFORMATS structure containing
1972 * information about the VBox formats available and the
1973 * clipboard context data. Must be freed by the worker.
1974 *
1975 * @thread X11 event thread.
1976 */
1977static void shClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval */)
1978{
1979 AssertPtrReturnVoid(pvUserData);
1980
1981 PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pvUserData;
1982 AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_REPORT_FORMATS);
1983
1984 PSHCLX11CTX pCtx = pReq->pCtx;
1985 SHCLFORMATS fFormats = pReq->Formats.fFormats;
1986
1987 RTMemFree(pReq);
1988
1989#ifdef LOG_ENABLED
1990 char *pszFmts = ShClFormatsToStrA(fFormats);
1991 AssertPtrReturnVoid(pszFmts);
1992 LogRel2(("Shared Clipboard: Reported available VBox formats %s to X11\n", pszFmts));
1993 RTStrFree(pszFmts);
1994#endif
1995
1996 clipInvalidateClipboardCache(pCtx);
1997 clipGrabX11Clipboard(pCtx, fFormats);
1998 clipResetX11Formats(pCtx);
1999
2000 LogFlowFuncLeave();
2001}
2002
2003/**
2004 * Announces new clipboard formats to the X11 clipboard.
2005 *
2006 * @returns VBox status code.
2007 * @param pCtx Context data for the clipboard backend.
2008 * @param uFormats Clipboard formats offered.
2009 */
2010int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS uFormats)
2011{
2012 /*
2013 * Immediately return if we are not connected to the X server.
2014 */
2015 if (!pCtx->fHaveX11)
2016 return VINF_SUCCESS;
2017
2018 int rc;
2019
2020 PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST));
2021 if (pReq)
2022 {
2023 pReq->enmType = SHCLX11EVENTTYPE_REPORT_FORMATS;
2024 pReq->pCtx = pCtx;
2025 pReq->Formats.fFormats = uFormats;
2026
2027 rc = clipThreadScheduleCall(pCtx, shClX11ReportFormatsToX11Worker, (XtPointer)pReq);
2028 if (RT_FAILURE(rc))
2029 RTMemFree(pReq);
2030 }
2031 else
2032 rc = VERR_NO_MEMORY;
2033
2034 LogFlowFuncLeaveRC(rc);
2035 return rc;
2036}
2037
2038#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2039/**
2040 * Converts an X11 data blob to a string list.
2041 *
2042 * @returns VBox status code.
2043 * @param pvData Data to conver to a string list.
2044 * @param cbData Size (in bytes) of \a pvData.
2045 * @param ppszList Where to return the allocated string list on success.
2046 * Must be free'd with RTStrFree().
2047 * @param pcbList Size (in bytes) of the returned string list on success.
2048 * Includes terminator.
2049 */
2050int ShClX11TransferConvertDataToStringList(const char *pvData, size_t cbData, char **ppszList, size_t *pcbList)
2051{
2052 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
2053 AssertReturn(cbData, VERR_INVALID_PARAMETER);
2054 AssertPtrNullReturn(ppszList, VERR_INVALID_POINTER);
2055 AssertPtrReturn(pcbList, VERR_INVALID_POINTER);
2056
2057 /* For URI lists we only accept valid UTF-8 encodings. */
2058 int rc = RTStrValidateEncodingEx((char *)pvData, cbData, 0 /* fFlags */);
2059 if (RT_FAILURE(rc))
2060 return rc;
2061
2062 /* We might need to skip some prefixes before actually reaching the file list. */
2063 static const char *s_aszPrefixesToSkip[] =
2064 { "copy\n" /* Nautilus / Nemo */ };
2065 for (size_t i = 0; i < RT_ELEMENTS(s_aszPrefixesToSkip); i++)
2066 {
2067 const char *pszNeedle = RTStrStr(pvData, s_aszPrefixesToSkip[i]);
2068 if (pszNeedle)
2069 {
2070 size_t const cbNeedle = strlen(s_aszPrefixesToSkip[i]);
2071 pszNeedle += cbNeedle;
2072 pvData = pszNeedle;
2073 Assert(cbData >= cbNeedle);
2074 cbData -= cbNeedle;
2075 }
2076 }
2077
2078 *pcbList = 0;
2079
2080# ifdef DEBUG_andy
2081 LogFlowFunc(("Data:\n%.*RhXd\n", cbData, pvData));
2082# endif
2083
2084 char **papszStrings;
2085 size_t cStrings;
2086 rc = RTStrSplit(pvData, cbData, SHCL_TRANSFER_URI_LIST_SEP_STR, &papszStrings, &cStrings);
2087 if (RT_SUCCESS(rc))
2088 {
2089 for (size_t i = 0; i < cStrings; i++)
2090 {
2091 const char *pszString = papszStrings[i];
2092 LogRel2(("Shared Clipboard: Received entry #%zu from X11: '%s'\n", i, pszString));
2093 rc = RTStrAAppend(ppszList, pszString);
2094 if (RT_FAILURE(rc))
2095 break;
2096 *pcbList += strlen(pszString);
2097 }
2098
2099 *pcbList++; /* Include terminator. */
2100 }
2101
2102 return rc;
2103}
2104#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
2105
2106/**
2107 * Worker function for clipConvertDataFromX11.
2108 *
2109 * Converts the data read from the X11 clipboard to the required format.
2110 * Signals the wait event.
2111 *
2112 * Converts the text obtained UTF-16LE with Windows EOLs.
2113 * Converts full BMP data to DIB format.
2114 *
2115 * @thread X11 event thread.
2116 */
2117SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, unsigned cbSrc)
2118{
2119 PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pClient;
2120 AssertPtrReturnVoid(pReq);
2121
2122 LogFlowFunc(("uFmtVBox=%#x, idxFmtX11=%u, pvSrc=%p, cbSrc=%u\n", pReq->Read.uFmtVBox, pReq->Read.idxFmtX11, pvSrc, cbSrc));
2123
2124 /* Sanity. */
2125 AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_READ);
2126 AssertReturnVoid(pReq->Read.uFmtVBox != VBOX_SHCL_FMT_NONE);
2127 AssertReturnVoid(pReq->Read.idxFmtX11 < SHCL_MAX_X11_FORMATS);
2128
2129 AssertPtrReturnVoid(pReq->pCtx);
2130
2131 LogRel2(("Shared Clipboard: Converting X11 format index %#x to VBox format %#x (%RU32 bytes max)\n",
2132 pReq->Read.idxFmtX11, pReq->Read.uFmtVBox, pReq->Read.cbMax));
2133
2134 int rc = VINF_SUCCESS;
2135
2136 void *pvDst = NULL;
2137 size_t cbDst = 0;
2138
2139 PSHCLX11CTX pCtx = pReq->pCtx;
2140 AssertPtr(pReq->pCtx);
2141
2142#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
2143 clipSetXtBusy(pCtx, false);
2144 if (clipGetXtNeedsUpdate(pCtx))
2145 clipQueryX11Targets(pCtx);
2146#endif
2147
2148 /* If X11 clipboard buffer has no data, libXt can pass to XtGetSelectionValue()
2149 * callback an empty string, in this case cbSrc is 0. */
2150 if (pvSrc == NULL || cbSrc == 0)
2151 {
2152 /* The clipboard selection may have changed before we could get it. */
2153 rc = VERR_NO_DATA;
2154 }
2155 else if (pReq->Read.uFmtVBox == VBOX_SHCL_FMT_UNICODETEXT)
2156 {
2157 /* In which format is the clipboard data? */
2158 switch (clipRealFormatForX11Format(pReq->Read.idxFmtX11))
2159 {
2160 case SHCLX11FMT_UTF8:
2161 RT_FALL_THROUGH();
2162 case SHCLX11FMT_TEXT:
2163 {
2164 size_t cwDst;
2165 /* If we are given broken UTF-8, we treat it as Latin1. */ /** @todo BUGBUG Is this acceptable? */
2166 if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0)))
2167 rc = ShClConvUtf8LFToUtf16CRLF((const char *)pvSrc, cbSrc,
2168 (PRTUTF16 *)&pvDst, &cwDst);
2169 else
2170 rc = ShClConvLatin1LFToUtf16CRLF((char *)pvSrc, cbSrc,
2171 (PRTUTF16 *)&pvDst, &cwDst);
2172 if (RT_SUCCESS(rc))
2173 {
2174 cwDst += 1 /* Include terminator */;
2175 cbDst = cwDst * sizeof(RTUTF16); /* Convert RTUTF16 units to bytes. */
2176
2177 LogFlowFunc(("UTF-16 text (%zu bytes):\n%ls\n", cbDst, pvDst));
2178 }
2179 break;
2180 }
2181
2182 default:
2183 {
2184 rc = VERR_INVALID_PARAMETER;
2185 break;
2186 }
2187 }
2188 }
2189 else if (pReq->Read.uFmtVBox == VBOX_SHCL_FMT_BITMAP)
2190 {
2191 /* In which format is the clipboard data? */
2192 switch (clipRealFormatForX11Format(pReq->Read.idxFmtX11))
2193 {
2194 case SHCLX11FMT_BMP:
2195 {
2196 const void *pDib;
2197 size_t cbDibSize;
2198 rc = ShClBmpGetDib((const void *)pvSrc, cbSrc,
2199 &pDib, &cbDibSize);
2200 if (RT_SUCCESS(rc))
2201 {
2202 pvDst = RTMemAlloc(cbDibSize);
2203 if (!pvDst)
2204 rc = VERR_NO_MEMORY;
2205 else
2206 {
2207 memcpy(pvDst, pDib, cbDibSize);
2208 cbDst = cbDibSize;
2209 }
2210 }
2211 break;
2212 }
2213
2214 default:
2215 {
2216 rc = VERR_INVALID_PARAMETER;
2217 break;
2218 }
2219 }
2220 }
2221 else if (pReq->Read.uFmtVBox == VBOX_SHCL_FMT_HTML)
2222 {
2223 /* In which format is the clipboard data? */
2224 switch (clipRealFormatForX11Format(pReq->Read.idxFmtX11))
2225 {
2226 case SHCLX11FMT_HTML:
2227 {
2228 /*
2229 * The common VBox HTML encoding will be - UTF-8
2230 * because it more general for HTML formats then UTF-16
2231 * X11 clipboard returns UTF-16, so before sending it we should
2232 * convert it to UTF-8.
2233 */
2234 pvDst = NULL;
2235 cbDst = 0;
2236
2237 /*
2238 * Some applications sends data in UTF-16, some in UTF-8,
2239 * without indication it in MIME.
2240 *
2241 * In case of UTF-16, at least [Open|Libre] Office adds an byte order mark (0xfeff)
2242 * at the start of the clipboard data.
2243 */
2244 if ( cbSrc >= sizeof(RTUTF16)
2245 && *(PRTUTF16)pvSrc == VBOX_SHCL_UTF16LEMARKER)
2246 {
2247 rc = ShClConvUtf16ToUtf8HTML((PRTUTF16)pvSrc, cbSrc / sizeof(RTUTF16), (char**)&pvDst, &cbDst);
2248 if (RT_SUCCESS(rc))
2249 {
2250 LogFlowFunc(("UTF-16 Unicode source (%u bytes):\n%ls\n\n", cbSrc, pvSrc));
2251 LogFlowFunc(("Byte Order Mark = %hx", ((PRTUTF16)pvSrc)[0]));
2252 LogFlowFunc(("UTF-8 Unicode dest (%u bytes):\n%s\n\n", cbDst, pvDst));
2253 }
2254 else
2255 LogRel(("Shared Clipboard: Converting UTF-16 Unicode failed with %Rrc\n", rc));
2256 }
2257 else /* Raw data. */
2258 {
2259 pvDst = RTMemAllocZ(cbSrc + 1 /* '\0' */);
2260 if(pvDst)
2261 {
2262 memcpy(pvDst, pvSrc, cbSrc);
2263 cbDst = cbSrc + 1 /* '\0' */;
2264 }
2265 else
2266 {
2267 rc = VERR_NO_MEMORY;
2268 break;
2269 }
2270 }
2271
2272 rc = VINF_SUCCESS;
2273 break;
2274 }
2275
2276 default:
2277 {
2278 rc = VERR_INVALID_PARAMETER;
2279 break;
2280 }
2281 }
2282 }
2283# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2284 else if (pReq->Read.uFmtVBox == VBOX_SHCL_FMT_URI_LIST)
2285 {
2286 /* In which format is the clipboard data? */
2287 switch (clipRealFormatForX11Format(pReq->Read.idxFmtX11))
2288 {
2289 case SHCLX11FMT_URI_LIST:
2290 RT_FALL_THROUGH();
2291 case SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES:
2292 RT_FALL_THROUGH();
2293 case SHCLX11FMT_URI_LIST_MATE_COPIED_FILES:
2294 RT_FALL_THROUGH();
2295 case SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD:
2296 RT_FALL_THROUGH();
2297 case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION:
2298 {
2299 rc = ShClX11TransferConvertDataToStringList((const char *)pvSrc, cbSrc, (char **)&pvDst, &cbDst);
2300 break;
2301 }
2302
2303 default:
2304 {
2305 AssertFailedStmt(rc = VERR_NOT_SUPPORTED); /* Missing code? */
2306 break;
2307 }
2308 }
2309 }
2310# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
2311 else
2312 rc = VERR_NOT_SUPPORTED;
2313
2314 LogFlowFunc(("pvDst=%p, cbDst=%RU32\n", pvDst, cbDst));
2315
2316 if (RT_FAILURE(rc))
2317 LogRel(("Shared Clipboard: Converting X11 format index %#x to VBox format %#x failed, rc=%Rrc\n",
2318 pReq->Read.idxFmtX11, pReq->Read.uFmtVBox, rc));
2319
2320 int rc2;
2321
2322 PSHCLEVENTPAYLOAD pPayload = NULL;
2323 size_t cbResp = sizeof(SHCLX11RESPONSE);
2324 PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)RTMemAllocZ(cbResp);
2325 if (pResp)
2326 {
2327 pResp->enmType = SHCLX11EVENTTYPE_READ;
2328 pResp->Read.pvData = pvDst;
2329 pResp->Read.cbData = cbDst;
2330
2331 pvDst = NULL; /* The response owns the data now. */
2332
2333 if ( pResp->Read.pvData
2334 && pResp->Read.cbData)
2335 {
2336 rc2 = ShClPayloadInit(0 /* ID, unused */, pResp, cbResp, &pPayload);
2337 AssertRC(rc2);
2338 }
2339 }
2340 else
2341 rc = VERR_NO_MEMORY;
2342
2343 rc2 = ShClEventSignal(pReq->pEvent, pPayload);
2344 if (RT_SUCCESS(rc2))
2345 pPayload = NULL; /* The event owns the payload now. */
2346
2347 if (pPayload) /* Free payload on error. */
2348 {
2349 ShClPayloadFree(pPayload);
2350 pPayload = NULL;
2351 }
2352
2353 LogRel2(("Shared Clipboard: Converting X11 clipboard data completed with %Rrc\n", rc));
2354
2355 RTMemFree(pReq);
2356 RTMemFree(pvDst);
2357
2358 LogFlowFuncLeaveRC(rc);
2359}
2360
2361/**
2362 * Converts the data read from the X11 clipboard to the required format.
2363 *
2364 * @thread X11 event thread.
2365 */
2366SHCL_X11_DECL(void) clipConvertDataFromX11(Widget widget, XtPointer pClient,
2367 Atom * /* selection */, Atom *atomType,
2368 XtPointer pvSrc, long unsigned int *pcLen,
2369 int *piFormat)
2370{
2371 RT_NOREF(widget);
2372
2373 int rc = VINF_SUCCESS;
2374
2375 if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */
2376 {
2377 LogRel(("Shared Clipboard: Reading clipboard data from X11 timed out\n"));
2378 rc = VERR_TIMEOUT;
2379 }
2380 else
2381 {
2382 PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pClient;
2383 if (pReq) /* Give some more clues, if available. */
2384 {
2385 AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_READ);
2386 char *pszFmts = ShClFormatsToStrA(pReq->Read.uFmtVBox);
2387 AssertPtrReturnVoid(pszFmts);
2388 AssertReturnVoid(pReq->Read.idxFmtX11 < SHCL_MAX_X11_FORMATS); /* Paranoia, should be checked already by the caller. */
2389 LogRel2(("Shared Clipboard: Converting X11 format '%s' -> VBox format(s) '%s'\n", g_aFormats[pReq->Read.idxFmtX11].pcszAtom, pszFmts));
2390 RTStrFree(pszFmts);
2391
2392 if (pReq->pCtx->Callbacks.pfnOnClipboardRead) /* Usually only used for testcases. */
2393 {
2394 void *pvData = NULL;
2395 size_t cbData = 0;
2396 rc = pReq->pCtx->Callbacks.pfnOnClipboardRead(pReq->pCtx->pFrontend, pReq->Read.uFmtVBox, &pvData, &cbData, NULL);
2397 if (RT_SUCCESS(rc))
2398 {
2399 /* Feed to conversion worker. */
2400 clipConvertDataFromX11Worker(pClient, pvData, cbData);
2401 RTMemFree(pvData);
2402 }
2403 }
2404 else /* Call conversion worker with current data provided by X (default). */
2405 clipConvertDataFromX11Worker(pClient, pvSrc, (*pcLen) * (*piFormat) / 8);
2406 }
2407 else
2408 rc = VERR_INVALID_POINTER;
2409 }
2410
2411 if (RT_FAILURE(rc))
2412 {
2413 LogRel(("Shared Clipboard: Reading clipboard data from X11 failed with %Rrc\n", rc));
2414
2415 /* Make sure to complete the request in any case by calling the conversion worker. */
2416 clipConvertDataFromX11Worker(pClient, NULL, 0);
2417 }
2418
2419 XtFree((char *)pvSrc);
2420}
2421
2422/**
2423 * Requests the current clipboard data from a specific selection.
2424 *
2425 * @returns VBox status code.
2426 * @param pCtx The X11 clipboard context to use.
2427 * @param pszWhere Clipboard selection to request the data from.
2428 * @param idxFmt The X11 format to request the data in.
2429 * @param pReq Where to store the requested data on success.
2430 */
2431static int clipGetSelectionValueEx(PSHCLX11CTX pCtx, const char *pszWhere, SHCLX11FMTIDX idxFmt,
2432 PSHCLX11REQUEST pReq)
2433{
2434 AssertPtrReturn(pszWhere, VERR_INVALID_POINTER);
2435 AssertReturn(idxFmt < SHCL_MAX_X11_FORMATS, VERR_INVALID_PARAMETER);
2436 AssertReturn(clipIsSupportedSelectionType(pCtx, clipGetAtom(pCtx, pszWhere)), VERR_INVALID_PARAMETER);
2437 AssertPtrReturn(pReq, VERR_INVALID_POINTER);
2438
2439 LogRel2(("Shared Clipboard: Requesting X11 selection value in %s for format '%s'\n", pszWhere, g_aFormats[idxFmt].pcszAtom));
2440
2441#ifndef TESTCASE
2442 XtGetSelectionValue(pCtx->pWidget, clipGetAtom(pCtx, pszWhere),
2443 clipAtomForX11Format(pCtx, idxFmt),
2444 clipConvertDataFromX11,
2445 reinterpret_cast<XtPointer>(pReq),
2446 CurrentTime);
2447#else
2448 tstClipRequestData(pCtx, idxFmt, (void *)pReq);
2449#endif
2450
2451 return VINF_SUCCESS; /** @todo Return real rc. */
2452}
2453
2454/**
2455 * Requests the current clipboard data from the CLIPBOARD selection.
2456 *
2457 * @returns VBox status code.
2458 * @param pCtx The X11 clipboard context to use.
2459 * @param idxFmt The X11 format to request the data in.
2460 * @param pReq Where to store the requested data on success.
2461 *
2462 * @sa clipGetSelectionValueEx() for requesting data for a specific selection.
2463 */
2464static int clipGetSelectionValue(PSHCLX11CTX pCtx, SHCLX11FMTIDX idxFmt, PSHCLX11REQUEST pReq)
2465{
2466 return clipGetSelectionValueEx(pCtx, "CLIPBOARD", idxFmt, pReq);
2467}
2468
2469/**
2470 * Worker function for ShClX11ReadDataFromX11Async.
2471 *
2472 * @param pvUserData Pointer to a CLIPREADX11CBREQ structure containing
2473 * information about the clipboard read request.
2474 * Must be free'd by the worker.
2475 * @thread X11 event thread.
2476 */
2477static void ShClX11ReadDataFromX11Worker(void *pvUserData, void * /* interval */)
2478{
2479 AssertPtrReturnVoid(pvUserData);
2480
2481 PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pvUserData;
2482 AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_READ);
2483 SHCLX11CTX *pCtx = pReq->pCtx;
2484 AssertPtrReturnVoid(pCtx);
2485
2486 LogFlowFunc(("pReq->uFmtVBox=%#x, idxFmtX11=%#x\n", pReq->Read.uFmtVBox, pReq->Read.idxFmtX11));
2487
2488 int rc = VERR_NO_DATA; /* VBox thinks we have data and we don't. */
2489
2490#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
2491 const bool fXtBusy = clipGetXtBusy(pCtx);
2492 clipSetXtBusy(pCtx, true);
2493 if (fXtBusy)
2494 {
2495 /* If the clipboard is busy just fend off the request. */
2496 rc = VERR_TRY_AGAIN;
2497 }
2498 else
2499#endif
2500 if (pReq->Read.uFmtVBox & VBOX_SHCL_FMT_UNICODETEXT)
2501 {
2502 pReq->Read.idxFmtX11 = pCtx->idxFmtText;
2503 if (pReq->Read.idxFmtX11 != SHCLX11FMT_INVALID)
2504 {
2505 /* Send out a request for the data to the current clipboard owner. */
2506 rc = clipGetSelectionValue(pCtx, pCtx->idxFmtText, pReq);
2507 }
2508 }
2509 else if (pReq->Read.uFmtVBox & VBOX_SHCL_FMT_BITMAP)
2510 {
2511 pReq->Read.idxFmtX11 = pCtx->idxFmtBmp;
2512 if (pReq->Read.idxFmtX11 != SHCLX11FMT_INVALID)
2513 {
2514 /* Send out a request for the data to the current clipboard owner. */
2515 rc = clipGetSelectionValue(pCtx, pCtx->idxFmtBmp, pReq);
2516 }
2517 }
2518 else if (pReq->Read.uFmtVBox & VBOX_SHCL_FMT_HTML)
2519 {
2520 pReq->Read.idxFmtX11 = pCtx->idxFmtHTML;
2521 if (pReq->Read.idxFmtX11 != SHCLX11FMT_INVALID)
2522 {
2523 /* Send out a request for the data to the current clipboard owner. */
2524 rc = clipGetSelectionValue(pCtx, pCtx->idxFmtHTML, pReq);
2525 }
2526 }
2527#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2528 else if (pReq->Read.uFmtVBox & VBOX_SHCL_FMT_URI_LIST)
2529 {
2530 pReq->Read.idxFmtX11 = pCtx->idxFmtURI;
2531 if (pReq->Read.idxFmtX11 != SHCLX11FMT_INVALID)
2532 {
2533 /* Send out a request for the data to the current clipboard owner. */
2534 rc = clipGetSelectionValue(pCtx, pCtx->idxFmtURI, pReq);
2535 }
2536 }
2537#endif
2538 else
2539 {
2540#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY
2541 clipSetXtBusy(pCtx, false);
2542#endif
2543 rc = VERR_NOT_IMPLEMENTED;
2544 }
2545
2546 /* If the above stuff fails, make sure to let the waiters know.
2547 *
2548 * Getting the actual selection value via clipGetSelectionValue[Ex]() above will happen in the X event thread,
2549 * which has its own signalling then. So this check only handles errors which happens before we put anything
2550 * onto the X event thread.
2551 */
2552 if (RT_FAILURE(rc))
2553 {
2554 int rc2 = ShClEventSignalEx(pReq->pEvent, rc, NULL /* Payload */);
2555 AssertRC(rc2);
2556 }
2557
2558 LogRel2(("Shared Clipboard: Reading X11 clipboard data completed with %Rrc\n", rc));
2559
2560 LogFlowFuncLeaveRC(rc);
2561}
2562
2563/**
2564 * Reads the X11 clipboard (asynchronously).
2565 *
2566 * @returns VBox status code.
2567 * @retval VERR_NO_DATA if format is supported but no data is available currently.
2568 * @retval VERR_NOT_IMPLEMENTED if the format is not implemented.
2569 * @param pCtx Context data for the clipboard backend.
2570 * @param uFmt The format that the VBox would like to receive the data in.
2571 * @param cbMax Maximum data to read (in bytes).
2572 * Specify UINT32_MAX to read as much as available.
2573 * @param pEvent Event to use for waiting for data to arrive.
2574 * The event's payload will contain the data read. Needs to be free'd with ShClEventRelease().
2575 */
2576int ShClX11ReadDataFromX11Async(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, uint32_t cbMax, PSHCLEVENT pEvent)
2577{
2578 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
2579 /*
2580 * Immediately return if we are not connected to the X server.
2581 */
2582 if (!pCtx->fHaveX11)
2583 return VERR_NO_DATA;
2584
2585 int rc = VINF_SUCCESS;
2586
2587 PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST));
2588 if (pReq)
2589 {
2590 pReq->enmType = SHCLX11EVENTTYPE_READ;
2591 pReq->pCtx = pCtx;
2592 pReq->Read.uFmtVBox = uFmt;
2593 pReq->Read.cbMax = cbMax;
2594 pReq->pEvent = pEvent;
2595
2596 /* We use this to schedule a worker function on the event thread. */
2597 rc = clipThreadScheduleCall(pCtx, ShClX11ReadDataFromX11Worker, (XtPointer)pReq);
2598 if (RT_FAILURE(rc))
2599 RTMemFree(pReq);
2600 }
2601 else
2602 rc = VERR_NO_MEMORY;
2603
2604 LogFlowFuncLeaveRC(rc);
2605 return rc;
2606}
2607
2608/**
2609 * Reads the X11 clipboard.
2610 *
2611 * @returns VBox status code.
2612 * @retval VERR_NO_DATA if format is supported but no data is available currently.
2613 * @retval VERR_NOT_IMPLEMENTED if the format is not implemented.
2614 * @param pCtx Context data for the clipboard backend.
2615 * @param pEventSource Event source to use.
2616 * @param msTimeout Timeout (in ms) for waiting.
2617 * @param uFmt The format that the VBox would like to receive the data in.
2618 * @param pvBuf Where to store the received data on success.
2619 * @param cbBuf Size (in bytes) of \a pvBuf. Also marks maximum data to read (in bytes).
2620 * @param pcbRead Where to return the read bytes on success.
2621 */
2622int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout,
2623 SHCLFORMAT uFmt, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2624{
2625 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
2626 AssertPtrReturn(pEventSource, VERR_INVALID_POINTER);
2627 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2628 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2629 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2630
2631 PSHCLEVENT pEvent;
2632 int rc = ShClEventSourceGenerateAndRegisterEvent(pEventSource, &pEvent);
2633 if (RT_SUCCESS(rc))
2634 {
2635 rc = ShClX11ReadDataFromX11Async(pCtx, uFmt, cbBuf, pEvent);
2636 if (RT_SUCCESS(rc))
2637 {
2638 int rcEvent;
2639 PSHCLEVENTPAYLOAD pPayload;
2640 rc = ShClEventWaitEx(pEvent, msTimeout, &rcEvent, &pPayload);
2641 if (RT_SUCCESS(rc))
2642 {
2643 if (pPayload)
2644 {
2645 AssertReturn(pPayload->cbData == sizeof(SHCLX11RESPONSE), VERR_INVALID_PARAMETER);
2646 AssertPtrReturn(pPayload->pvData, VERR_INVALID_POINTER);
2647 PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)pPayload->pvData;
2648 AssertReturn(pResp->enmType == SHCLX11EVENTTYPE_READ, VERR_INVALID_PARAMETER);
2649
2650 memcpy(pvBuf, pResp->Read.pvData, RT_MIN(cbBuf, pResp->Read.cbData));
2651 *pcbRead = pResp->Read.cbData;
2652
2653 RTMemFree(pResp->Read.pvData);
2654 pResp->Read.cbData = 0;
2655
2656 ShClPayloadFree(pPayload);
2657 }
2658 else /* No payload given; could happen on invalid / not-expected formats. */
2659 {
2660 rc = VERR_NO_DATA;
2661 *pcbRead = 0;
2662 }
2663 }
2664 else if (rc == VERR_SHCLPB_EVENT_FAILED)
2665 rc = rcEvent;
2666 }
2667 }
2668
2669 LogFlowFuncLeaveRC(rc);
2670 return rc;
2671}
2672
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette