VirtualBox

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

Last change on this file since 103131 was 102929, checked in by vboxsync, 14 months ago

Shared Clipboard: Better documented for what headless mode is and why we need it; improved testing here and there a bit, so we have better coverage there.

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

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