VirtualBox

Ignore:
Timestamp:
Aug 11, 2009 4:41:38 PM (16 years ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
50961
Message:

GuestHost/SharedClipboard: use the XFIXES notification extension in the X11 shared clipboard instead of polling

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp

    r21651 r22181  
    2121 */
    2222
    23 /* Note: to automatically run regression tests on the shared clipboard, set
    24  * the make variable VBOX_RUN_X11_CLIPBOARD_TEST=1 while building.  If you
    25  * often make changes to the clipboard code, setting this variable in
    26  * LocalConfig.kmk will cause the tests to be run every time the code is
     23/* Note: to automatically run regression tests on the shared clipboard,
     24 * execute the tstClipboardX11 testcase.  If you often make changes to the
     25 * clipboard code, adding the line
     26 *   OTHERS += $(PATH_tstClipboardX11)/tstClipboardX11.run
     27 * to LocalConfig.kmk will cause the tests to be run every time the code is
    2728 * changed. */
    2829
     
    3132#include <errno.h>
    3233
     34#include <dlfcn.h>
     35#include <fcntl.h>
    3336#include <unistd.h>
    3437
     
    4447#include <X11/StringDefs.h>
    4548
     49#include <iprt/types.h>
    4650#include <iprt/env.h>
    4751#include <iprt/mem.h>
     
    164168    Widget widget;
    165169
    166     /** Does VBox currently own the clipboard?  If so, we don't need to poll
    167      * X11 for supported formats. */
     170    /** Does VBox currently own the clipboard? */
    168171    bool fOwnsClipboard;
     172    /** Should we try to grab the clipboard on startup? */
     173    bool fGrabClipboardOnStart;
    169174
    170175    /** The best text format X11 has to offer, as an index into the formats
     
    191196    /** The reader end of the pipe */
    192197    int wakeupPipeRead;
     198    /** A pointer to the XFixesSelectSelectionInput function */
     199    void (*fixesSelectInput)(Display *, Window, Atom, unsigned long);
     200    /** The first XFixes event number */
     201    int fixesEventBase;
    193202};
    194203
     
    280289}
    281290
    282 static void clipQueueToEventThread(XtAppContext app_context,
     291static void clipQueueToEventThread(CLIPBACKEND *pCtx,
    283292                                   XtTimerCallbackProc proc,
    284293                                   XtPointer client_data);
     294
     295/** String written to the wakeup pipe. */
     296#define WAKE_UP_STRING      "WakeUp!"
     297/** Length of the string written. */
     298#define WAKE_UP_STRING_LEN  ( sizeof(WAKE_UP_STRING) - 1 )
     299
    285300#ifndef TESTCASE
    286 void clipQueueToEventThread(XtAppContext app_context,
     301/** Schedule a function call to run on the Xt event thread by passing it to
     302 * the application context as a 0ms timeout and waking up the event loop by
     303 * writing to the wakeup pipe which it monitors. */
     304void clipQueueToEventThread(CLIPBACKEND *pCtx,
    287305                            XtTimerCallbackProc proc,
    288306                            XtPointer client_data)
    289307{
    290     XtAppAddTimeOut(app_context, 0, proc, client_data);
     308    LogRel2(("clipQueueToEventThread: proc=%p, client_data=%p\n",
     309             proc, client_data));
     310    XtAppAddTimeOut(pCtx->appContext, 0, proc, client_data);
     311    write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN);
    291312}
    292313#endif
     
    302323
    303324/**
    304  * Forget which formats were previously in the X11 clipboard.  Should be
    305  * called when we grab the clipboard, so that when we lose it again the poller
    306  * will notify us when new formats become available. */
     325 * Forget which formats were previously in the X11 clipboard.  Called when we
     326 * grab the clipboard. */
    307327static void clipResetX11Formats(CLIPBACKEND *pCtx)
    308328{
     
    425445    bool changed = false;
    426446
    427     LogRel3 (("%s: called\n", __PRETTY_FUNCTION__));
     447    LogRel2 (("%s: called\n", __PRETTY_FUNCTION__));
    428448    if (pCtx->fOwnsClipboard)
    429449        /* VBox raced us and we lost.  So we don't want to report anything. */
    430450        return;
    431451    clipGetFormatsFromTargets(pCtx, pTargets, cTargets, &changed);
    432     if (changed)
    433         clipReportFormatsToVBox(pCtx);
     452    clipReportFormatsToVBox(pCtx);
    434453}
    435454
     
    460479 * Notify the VBox clipboard about available data formats, based on the
    461480 * "targets" information obtained from the X11 clipboard.
    462  * @note  callback for XtGetSelectionValue, called on a polling loop
     481 * @note  callback for XtGetSelectionValue
    463482 */
    464483static void clipConvertX11Targets(Widget, XtPointer pClientData,
     
    472491                                                    : (Atom *)pValue;
    473492    size_t cTargets = pTargets ? *pcLen : 0;
     493    LogRel2(("clipConvertX11Targets: pValue=%p, *pcLen=%u, *atomType=%d, XT_CONVERT_FAIL=%d\n",
     494             pValue, *pcLen, *atomType, XT_CONVERT_FAIL));
    474495    clipUpdateX11Targets(pCtx, pTargets, cTargets);
    475496    XtFree(reinterpret_cast<char *>(pValue));
    476497}
    477498
    478 enum { TIMER_FREQ = 200 /* ms */ };
    479 
    480 static void clipPollX11CBFormats(XtPointer pUserData,
    481                                  XtIntervalId * /* hTimerId */);
    482 static void clipSchedulePoller(CLIPBACKEND *pCtx,
    483                                XtTimerCallbackProc proc);
    484 
    485 #ifndef TESTCASE
    486 void clipSchedulePoller(CLIPBACKEND *pCtx,
    487                         XtTimerCallbackProc proc)
    488 {
    489     XtAppAddTimeOut(pCtx->appContext, TIMER_FREQ, proc, pCtx);
    490 }
    491 #endif
    492 
    493 /**
    494  * This timer callback is called every 200ms to check the contents of the X11
    495  * clipboard.
    496  * @note  X11 backend code, callback for XtAppAddTimeOut, recursively
    497  *        re-armed.
    498  * @todo  Use the XFIXES extension to check for new clipboard data when
    499  *        available.
    500  */
    501 void clipPollX11CBFormats(XtPointer pUserData, XtIntervalId * /* hTimerId */)
    502 {
    503     CLIPBACKEND *pCtx = (CLIPBACKEND *)pUserData;
    504     LogRel3 (("%s: called\n", __PRETTY_FUNCTION__));
     499/**
     500 * Callback to notify us when the contents of the X11 clipboard change.
     501 */
     502void clipQueryX11CBFormats(CLIPBACKEND *pCtx)
     503{
     504    LogRel2 (("%s: called\n", __PRETTY_FUNCTION__));
    505505    /* Get the current clipboard contents if we don't own it ourselves */
    506506    if (!pCtx->fOwnsClipboard)
    507507    {
    508         LogRel3 (("%s: requesting the targets that the X11 clipboard offers\n",
     508        LogRel2 (("%s: requesting the targets that the X11 clipboard offers\n",
    509509               __PRETTY_FUNCTION__));
    510510        XtGetSelectionValue(pCtx->widget,
     
    514514                            CurrentTime);
    515515    }
    516     /* Re-arm our timer */
    517     clipSchedulePoller(pCtx, clipPollX11CBFormats);
    518516}
    519517
    520518#ifndef TESTCASE
     519/**
     520 * Wait until an event arrives and handle it if it is an XFIXES selection
     521 * event, which Xt doesn't know about.
     522 */
     523void clipPeekEventAndDoXFixesHandling(CLIPBACKEND *pCtx)
     524{
     525    XEvent event = { 0 };
     526
     527    if (XtAppPeekEvent(pCtx->appContext, &event))
     528        if (event.type == pCtx->fixesEventBase)
     529            clipQueryX11CBFormats(pCtx);
     530}
     531
    521532/**
    522533 * The main loop of our clipboard reader.
     
    528539
    529540    CLIPBACKEND *pCtx = (CLIPBACKEND *)pvUser;
     541
     542    if (pCtx->fGrabClipboardOnStart)
     543        clipQueryX11CBFormats(pCtx);
    530544    while (XtAppGetExitFlag(pCtx->appContext) == FALSE)
     545    {
     546        clipPeekEventAndDoXFixesHandling(pCtx);
    531547        XtAppProcessEvent(pCtx->appContext, XtIMAll);
     548    }
    532549    LogRel(("Shared clipboard: shared clipboard thread terminated successfully\n"));
    533550    return VINF_SUCCESS;
     
    562579/** Worker function for stopping the clipboard which runs on the event
    563580 * thread. */
    564 static void clipStopEventThreadWorker(XtPointer pUserData, int * /* source */,
    565                                       XtInputId * /* id */)
     581static void clipStopEventThreadWorker(XtPointer pUserData, XtIntervalId *)
    566582{
    567583   
     
    575591     * the VBox part *must* have returned before we do this. */
    576592    XtAppSetExitFlag(pCtx->appContext);
     593}
     594
     595#ifndef TESTCASE
     596/** Setup the XFixes library and load the XFixesSelectSelectionInput symbol */
     597static int clipLoadXFixes(Display *pDisplay, CLIPBACKEND *pCtx)
     598{
     599    int dummy1 = 0, dummy2 = 0, rc = VINF_SUCCESS;
     600    void *hFixesLib;
     601
     602    hFixesLib = dlopen("libXfixes.so.1", RTLD_LAZY);
     603    if (!hFixesLib)
     604        hFixesLib = dlopen("libXfixes.so.2", RTLD_LAZY);
     605    if (!hFixesLib)
     606        hFixesLib = dlopen("libXfixes.so.3", RTLD_LAZY);
     607    if (hFixesLib)
     608        pCtx->fixesSelectInput =
     609            (void (*)(Display *, Window, Atom, long unsigned int))
     610                (uintptr_t)dlsym(hFixesLib, "XFixesSelectSelectionInput");
     611    /* For us, a NULL function pointer is a failure */
     612    if (!hFixesLib || !pCtx->fixesSelectInput)
     613        rc = VERR_NOT_SUPPORTED;
     614    if (   RT_SUCCESS(rc)
     615        && !XQueryExtension(pDisplay, "XFIXES", &dummy1,
     616                            &pCtx->fixesEventBase, &dummy2))
     617        rc = VERR_NOT_SUPPORTED;
     618    if (RT_SUCCESS(rc) && pCtx->fixesEventBase < 0)
     619        rc = VERR_NOT_SUPPORTED;
     620    return rc;
     621}
     622#endif
     623
     624/** This is the callback which is scheduled when data is available on the
     625 * wakeup pipe.  It simply reads all data from the pipe. */
     626static void clipDrainWakeupPipe(XtPointer pUserData, int *, XtInputId *)
     627{
     628    CLIPBACKEND *pCtx = (CLIPBACKEND *)pUserData;
     629    char acBuf[WAKE_UP_STRING_LEN];
     630
     631    LogRel2(("clipDrainWakeupPipe: called\n"));
     632    while (read(pCtx->wakeupPipeRead, acBuf, sizeof(acBuf)) > 0) {}
    577633}
    578634
     
    601657        rc = VERR_NOT_SUPPORTED;
    602658    }
     659#ifndef TESTCASE
     660    if (RT_SUCCESS(rc))
     661        rc = clipLoadXFixes(pDisplay, pCtx);
     662#endif
    603663    if (RT_SUCCESS(rc))
    604664    {
     
    617677    if (RT_SUCCESS(rc))
    618678    {
     679        EventMask mask = 0;
     680
    619681        XtSetMappedWhenManaged(pCtx->widget, false);
    620682        XtRealizeWidget(pCtx->widget);
    621         /* Set up a timer to poll the X11 clipboard */
    622         clipSchedulePoller(pCtx, clipPollX11CBFormats);
     683#ifndef TESTCASE
     684        /* Enable clipboard update notification */
     685        pCtx->fixesSelectInput(pDisplay, XtWindow(pCtx->widget),
     686                               clipGetAtom(pCtx->widget, "CLIPBOARD"),
     687                               7 /* All XFixes*Selection*NotifyMask flags */);
     688#endif
    623689    }
    624690    /* Create the pipes */
     
    630696        if (!XtAppAddInput(pCtx->appContext, pCtx->wakeupPipeRead,
    631697                           (XtPointer) XtInputReadMask,
    632                            clipStopEventThreadWorker, (XtPointer) pCtx))
     698                           clipDrainWakeupPipe, (XtPointer) pCtx))
    633699            rc = VERR_NO_MEMORY;  /* What failure means is not doc'ed. */
     700        if (   RT_SUCCESS(rc)
     701            && (fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK) != 0))
     702            rc = RTErrConvertFromErrno(errno);
    634703    }
    635704    else
     
    691760/**
    692761 * Announce to the X11 backend that we are ready to start.
    693  */
    694 int ClipStartX11(CLIPBACKEND *pCtx)
     762 * @param  grab  whether we should try to grab the shared clipboard at once
     763 */
     764int ClipStartX11(CLIPBACKEND *pCtx, bool grab)
    695765{
    696766    int rc = VINF_SUCCESS;
     
    703773
    704774    rc = clipInit(pCtx);
     775    if (RT_SUCCESS(rc))
     776    {
     777        pCtx->fOwnsClipboard = false;
     778        clipResetX11Formats(pCtx);
     779        pCtx->fGrabClipboardOnStart = grab;
     780    }
    705781#ifndef TESTCASE
    706782    if (RT_SUCCESS(rc))
     
    712788    }
    713789#endif
    714     if (RT_SUCCESS(rc))
    715     {
    716         pCtx->fOwnsClipboard = false;
    717         clipResetX11Formats(pCtx);
    718     }
    719790    return rc;
    720791}
    721 
    722 /** String written to the wakeup pipe. */
    723 #define WAKE_UP_STRING      "WakeUp!"
    724 /** Length of the string written. */
    725 #define WAKE_UP_STRING_LEN  ( sizeof(WAKE_UP_STRING) - 1 )
    726792
    727793/**
     
    745811    LogRelFunc(("stopping the shared clipboard X11 backend\n"));
    746812    /* Write to the "stop" pipe */
    747     rc = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN);
     813    clipQueueToEventThread(pCtx, clipStopEventThreadWorker, (XtPointer) pCtx);
     814#ifndef TESTCASE
    748815    do
    749816    {
     
    752819        Assert(RT_SUCCESS(rc) || ((VERR_TIMEOUT == rc) && (count != 5)));
    753820    } while ((VERR_TIMEOUT == rc) && (count < 300));
     821#else
     822    rc = VINF_SUCCESS;
     823    rcThread = VINF_SUCCESS;
     824#endif
    754825    if (RT_SUCCESS(rc))
    755826        AssertRC(rcThread);
     
    10901161        rc = clipCreateX11Targets(pCtx, atomTypeReturn, pValReturn,
    10911162                                  pcLenReturn, piFormatReturn);
    1092     else
    10931163        rc = clipConvertVBoxCBForX11(pCtx, atomTarget, atomTypeReturn,
    10941164                                     pValReturn, pcLenReturn, piFormatReturn);
     
    11031173{
    11041174    LogRelFlowFunc (("\n"));
    1105     /* The formats should be set to the right values as soon as we start
    1106      * polling */
     1175    /* The formats should be set to the right values as soon as X11 clipboard
     1176     * data becomes available. */
    11071177    clipReportEmptyX11CB(pCtx);
    11081178    pCtx->fOwnsClipboard = false;
     
    12031273        pFormats->pCtx = pCtx;
    12041274        pFormats->formats = u32Formats;
    1205         clipQueueToEventThread(pCtx->appContext, clipNewVBoxFormatsWorker,
     1275        clipQueueToEventThread(pCtx, clipNewVBoxFormatsWorker,
    12061276                               (XtPointer) pFormats);
    12071277    }
     
    14971567    RTMemFree(pvDest);
    14981568    RTMemFree(pReq);
    1499     if (RT_SUCCESS(rc))
    1500         /* The other end may want to cache the data, so pretend we have new
    1501          * data, as we have no way of telling when new data really does
    1502          * arrive. */
    1503         clipReportFormatsToVBox(pCtx);
    1504     // else
    1505     //     clipReportEmptyX11CB(pCtx);
    15061569    LogRelFlowFunc(("rc=%Rrc\n", rc));
    15071570}
     
    15861649        pX11Req->mReq = pReq;
    15871650        /* We use this to schedule a worker function on the event thread. */
    1588         clipQueueToEventThread(pCtx->appContext, vboxClipboardReadX11Worker,
     1651        clipQueueToEventThread(pCtx, vboxClipboardReadX11Worker,
    15891652                               (XtPointer) pX11Req);
    15901653    }
     
    16071670#define TEST_WIDGET (Widget)0xffff
    16081671
    1609 /* Our X11 clipboard target poller */
    1610 static XtTimerCallbackProc g_pfnPoller = NULL;
    1611 /* User data for the poller function. */
    1612 static XtPointer g_pPollerData = NULL;
    1613 
    1614 /* For the testcase, we install the poller function in a global variable
    1615  * which is called when the testcase updates the X11 targets. */
    1616 void clipSchedulePoller(CLIPBACKEND *pCtx,
    1617                         XtTimerCallbackProc proc)
    1618 {
    1619     g_pfnPoller = proc;
    1620     g_pPollerData = (XtPointer)pCtx;
    1621 }
    1622 
    16231672/* For the purpose of the test case, we just execute the procedure to be
    16241673 * scheduled, as we are running single threaded. */
    1625 void clipQueueToEventThread(XtAppContext app_context,
     1674void clipQueueToEventThread(CLIPBACKEND *pCtx,
    16261675                            XtTimerCallbackProc proc,
    16271676                            XtPointer client_data)
     
    25702619    if (!testStringFromVBoxFailed(pCtx, "UTF8_STRING"))
    25712620        ++cErrs;
     2621    rc = ClipStopX11(pCtx);
     2622    AssertRCReturn(rc, 1);
     2623    ClipDestructX11(pCtx);
    25722624
    25732625    if (cErrs > 0)
Note: See TracChangeset for help on using the changeset viewer.

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