VirtualBox

Ignore:
Timestamp:
Jun 19, 2023 9:11:37 AM (21 months ago)
Author:
vboxsync
svn:sync-xref-src-repo-rev:
157911
Message:

Shared Clipboard: Unified root list entry code to also use the generic list entry code, a lot of updates for the cross OS transfer handling code, more updates for HTTP server transfer handling.

This also changed the handling of how that transfers are being initiated, as we needed to have this for X11: Before, transfers were initiated as soon as on side announced the URI list format -- now we postpone initiating the transfer until the receiving side requests the data as URI list.

bugref:9437

Location:
trunk/src/VBox/Additions/WINNT/VBoxTray
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Additions/WINNT/VBoxTray/VBoxClipboard.cpp

    r100090 r100204  
    9393*********************************************************************************************************************************/
    9494#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    95 static DECLCALLBACK(int)  vboxClipboardOnTransferInitCallback(PSHCLTXPROVIDERCTX pCtx);
    96 static DECLCALLBACK(int)  vboxClipboardOnTransferStartCallback(PSHCLTXPROVIDERCTX pCtx);
    97 static DECLCALLBACK(void) vboxClipboardOnTransferCompleteCallback(PSHCLTXPROVIDERCTX pCtx, int rc);
    98 static DECLCALLBACK(void) vboxClipboardOnTransferErrorCallback(PSHCLTXPROVIDERCTX pCtx, int rc);
     95static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTXPROVIDERCTX pCtx);
     96static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTXPROVIDERCTX pCtx);
     97static DECLCALLBACK(void) vbtrShClTransferErrorCallback(PSHCLTXPROVIDERCTX pCtx, int rc);
    9998#endif
    10099
     
    108107 * @param   pTransfer           Pointer to transfer to cleanup.
    109108 */
    110 static void vboxClipboardTransferCallbackCleanup(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer)
     109static void vbtrShClTransferCallbackCleanup(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer)
    111110{
    112111    LogFlowFuncEnter();
     
    130129}
    131130
    132 /** @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnInitialize */
    133 static DECLCALLBACK(int) vboxClipboardOnTransferInitCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
     131/**
     132 * Worker for a reading clipboard from the host.
     133 *
     134 * @thread  Main clipboard thread.
     135 */
     136static DECLCALLBACK(int) vbtrShClRequestDataFromSourceCallbackWorker(PSHCLCONTEXT pCtx,
     137                                                                     SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
     138{
     139    RT_NOREF(pvUser);
     140
     141    LogFlowFuncEnter();
     142
     143    int rc = VERR_NO_DATA; /* Play safe. */
     144
     145    uint32_t cbRead = 0;
     146
     147    uint32_t cbData = _4K; /** @todo Make this dynamic. */
     148    void    *pvData = RTMemAlloc(cbData);
     149    if (pvData)
     150    {
     151        rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
     152    }
     153    else
     154        rc = VERR_NO_MEMORY;
     155
     156    /*
     157     * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
     158     * larger buffer.  The size of the buffer needed is placed in *pcb.
     159     * So we start all over again.
     160     */
     161    if (rc == VINF_BUFFER_OVERFLOW)
     162    {
     163        /* cbRead contains the size required. */
     164
     165        cbData = cbRead;
     166        pvData = RTMemRealloc(pvData, cbRead);
     167        if (pvData)
     168        {
     169            rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
     170            if (rc == VINF_BUFFER_OVERFLOW)
     171                rc = VERR_BUFFER_OVERFLOW;
     172        }
     173        else
     174            rc = VERR_NO_MEMORY;
     175    }
     176
     177    if (!cbRead)
     178        rc = VERR_NO_DATA;
     179
     180    if (RT_SUCCESS(rc))
     181    {
     182        if (ppv)
     183            *ppv = pvData;
     184        if (pcb)
     185            *pcb = cbRead; /* Actual bytes read. */
     186    }
     187    else
     188    {
     189        /*
     190         * Catch other errors. This also catches the case in which the buffer was
     191         * too small a second time, possibly because the clipboard contents
     192         * changed half-way through the operation.  Since we can't say whether or
     193         * not this is actually an error, we just return size 0.
     194         */
     195        RTMemFree(pvData);
     196    }
     197
     198    LogFlowFuncLeaveRC(rc);
     199    return rc;
     200}
     201
     202/**
     203 * @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource
     204 *
     205 * Called from the IDataObject implementation to request data from the host.
     206 *
     207 * @thread  shclwnd thread.
     208 */
     209DECLCALLBACK(int) vbtrShClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
     210                                                        SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
     211{
     212    PRTREQ pReq = NULL;
     213    int rc = RTReqQueueCallEx(pCtx->Win.hReqQ, &pReq, SHCL_TIMEOUT_DEFAULT_MS, RTREQFLAGS_IPRT_STATUS,
     214                              (PFNRT)vbtrShClRequestDataFromSourceCallbackWorker, 5, pCtx, uFmt, ppv, pcb, pvUser);
     215    RTReqRelease(pReq);
     216    return rc;
     217}
     218
     219/**
     220 * @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnStart
     221 *
     222 * Called from VbglR3 (main thread) to notify the IDataObject.
     223 *
     224 * @thread  Clipboard main thread.
     225 */
     226static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
    134227{
    135228    PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
    136229    AssertPtr(pCtx);
    137230
    138     LogFlowFunc(("pCtx=%p\n", pCtx));
    139 
    140     RT_NOREF(pCtx);
    141 
    142     return VINF_SUCCESS;
    143 }
    144 
    145 /** @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnStart */
    146 static DECLCALLBACK(int) vboxClipboardOnTransferStartCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx)
     231    PSHCLTRANSFER pTransfer = pCbCtx->pTransfer;
     232    AssertPtr(pTransfer);
     233
     234    const SHCLTRANSFERDIR enmDir = ShClTransferGetDir(pTransfer);
     235
     236    LogFlowFunc(("pCtx=%p, idTransfer=%RU32, enmDir=%RU32\n", pCtx, ShClTransferGetID(pTransfer), enmDir));
     237
     238    int rc;
     239
     240    /* The guest wants to transfer data to the host. */
     241    if (enmDir == SHCLTRANSFERDIR_TO_REMOTE)
     242    {
     243        rc = SharedClipboardWinTransferGetRootsFromClipboard(&pCtx->Win, pTransfer);
     244    }
     245    /* The guest wants to transfer data from the host. */
     246    else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE)
     247    {
     248        rc = RTCritSectEnter(&pCtx->Win.CritSect);
     249        if (RT_SUCCESS(rc))
     250        {
     251            SharedClipboardWinDataObject *pObj = pCtx->Win.pDataObjInFlight;
     252            AssertPtrReturnVoid(pObj);
     253            rc = pObj->SetAndStartTransfer(pTransfer);
     254
     255            pCtx->Win.pDataObjInFlight = NULL; /* Hand off to Windows. */
     256
     257            int rc2 = RTCritSectLeave(&pCtx->Win.CritSect);
     258            AssertRC(rc2);
     259        }
     260    }
     261    else
     262        AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
     263
     264    if (RT_FAILURE(rc))
     265        LogRel(("Shared Clipboard: Starting transfer failed, rc=%Rrc\n", rc));
     266
     267    LogFlowFunc(("LEAVE: idTransfer=%RU32, rc=%Rrc\n", ShClTransferGetID(pTransfer), rc));
     268}
     269
     270/** @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnCompleted */
     271static DECLCALLBACK(void) vbtrShClTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rcCompletion)
    147272{
    148273    PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
    149274    AssertPtr(pCtx);
    150275
    151     PSHCLTRANSFER pTransfer = pCbCtx->pTransfer;
    152     AssertPtr(pTransfer);
    153 
    154     const SHCLTRANSFERDIR enmDir = ShClTransferGetDir(pTransfer);
    155 
    156     LogFlowFunc(("pCtx=%p, idTransfer=%RU32, enmDir=%RU32\n", pCtx, ShClTransferGetID(pTransfer), enmDir));
    157 
    158     int rc;
    159 
    160     /* The guest wants to write local data to the host? */
    161     if (enmDir == SHCLTRANSFERDIR_TO_REMOTE)
    162     {
    163         rc = SharedClipboardWinGetRoots(&pCtx->Win, pTransfer);
    164     }
    165     /* The guest wants to read data from a remote source. */
    166     else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE)
    167     {
    168         /* The IDataObject *must* be created on the same thread as our (proxy) window, so post a message to it
    169          * to do the stuff for us. */
    170         PSHCLEVENT pEvent;
    171         rc = ShClEventSourceGenerateAndRegisterEvent(&pTransfer->Events, &pEvent);
    172         if (RT_SUCCESS(rc))
    173         {
    174             /* Don't want to rely on SendMessage (synchronous) here, so just post and wait the event getting signalled. */
    175             ::PostMessage(pCtx->Win.hWnd, SHCL_WIN_WM_TRANSFER_START, (WPARAM)pTransfer, (LPARAM)pEvent->idEvent);
    176 
    177             PSHCLEVENTPAYLOAD pPayload;
    178             rc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload);
    179             if (RT_SUCCESS(rc))
    180             {
    181                 Assert(pPayload->cbData == sizeof(int));
    182                 rc = *(int *)pPayload->pvData;
    183 
    184                 ShClPayloadFree(pPayload);
    185             }
    186 
    187             ShClEventRelease(pEvent);
    188         }
    189         else
    190             AssertFailedStmt(rc = VERR_SHCLPB_MAX_EVENTS_REACHED);
    191     }
    192     else
    193         AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
    194 
    195     if (RT_FAILURE(rc))
    196         LogRel(("Shared Clipboard: Starting transfer failed, rc=%Rrc\n", rc));
    197 
    198     LogFlowFunc(("LEAVE: idTransfer=%RU32, rc=%Rrc\n", ShClTransferGetID(pTransfer), rc));
    199     return rc;
    200 }
    201 
    202 /** @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnCompleted */
    203 static DECLCALLBACK(void) vboxClipboardOnTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rcCompletion)
     276    LogRel2(("Shared Clipboard: Transfer to destination %s\n",
     277             rcCompletion == VERR_CANCELLED ? "canceled" : "complete"));
     278
     279    vbtrShClTransferCallbackCleanup(&pCtx->TransferCtx, pCbCtx->pTransfer);
     280}
     281
     282/** @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnError */
     283static DECLCALLBACK(void) vbtrShClTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rcError)
    204284{
    205285    PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
    206286    AssertPtr(pCtx);
    207287
    208     LogRel2(("Shared Clipboard: Transfer to destination %s\n",
    209              rcCompletion == VERR_CANCELLED ? "canceled" : "complete"));
    210 
    211     vboxClipboardTransferCallbackCleanup(&pCtx->TransferCtx, pCbCtx->pTransfer);
    212 }
    213 
    214 /** @copydoc SHCLTRANSFERCALLBACKTABLE::pfnOnError */
    215 static DECLCALLBACK(void) vboxClipboardOnTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rcError)
    216 {
    217     PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser;
    218     AssertPtr(pCtx);
    219 
    220288    LogRel(("Shared Clipboard: Transfer to destination failed with %Rrc\n", rcError));
    221289
    222     vboxClipboardTransferCallbackCleanup(&pCtx->TransferCtx, pCbCtx->pTransfer);
     290    vbtrShClTransferCallbackCleanup(&pCtx->TransferCtx, pCbCtx->pTransfer);
    223291}
    224292
    225293#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
    226294
    227 static LRESULT vboxClipboardWinProcessMsg(PSHCLCONTEXT pCtx, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     295static LRESULT vbtrShClWndProcWorker(PSHCLCONTEXT pCtx, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    228296{
    229297    AssertPtr(pCtx);
     
    335403        }
    336404
    337         case WM_RENDERFORMAT:
     405        case WM_RENDERFORMAT: /* Guest wants to render the clipboard data. */
    338406        {
    339407            LogFunc(("WM_RENDERFORMAT\n"));
     
    346414            LogFunc(("WM_RENDERFORMAT: cfFormat=%u -> fFormat=0x%x\n", cfFormat, fFormat));
    347415
     416#ifdef LOG_ENABLED
     417            char *pszFmts = ShClFormatsToStrA(fFormat);
     418            AssertPtrReturn(pszFmts, 0);
     419            LogRel(("Shared Clipboard: Rendering Windows format %#x as VBox format '%s'\n", cfFormat, pszFmts));
     420            RTStrFree(pszFmts);
     421#endif
    348422            if (fFormat == VBOX_SHCL_FMT_NONE)
    349423            {
    350                 LogFunc(("WM_RENDERFORMAT: Unsupported format requested\n"));
     424                LogRel(("Shared Clipboard: Unsupported format (%#x) requested\n", cfFormat));
    351425                SharedClipboardWinClear();
    352426            }
     
    535609        }
    536610
    537         case SHCL_WIN_WM_REPORT_FORMATS:
     611        case SHCL_WIN_WM_REPORT_FORMATS: /* Host reported clipboard formats. */
    538612        {
    539613            LogFunc(("SHCL_WIN_WM_REPORT_FORMATS\n"));
     
    546620            const SHCLFORMATS fFormats = pEvent->u.fReportedFormats;
    547621
     622#ifdef LOG_ENABLED
     623            char *pszFmts = ShClFormatsToStrA(fFormats);
     624            AssertPtrReturn(pszFmts, 0);
     625            LogRel(("Shared Clipboard: Host reported formats '%s'\n", pszFmts));
     626            RTStrFree(pszFmts);
     627#endif
    548628            if (fFormats != VBOX_SHCL_FMT_NONE) /* Could arrive with some older GA versions. */
    549629            {
    550 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    551                 if (fFormats & VBOX_SHCL_FMT_URI_LIST)
    552                 {
    553                     LogFunc(("VBOX_SHCL_FMT_URI_LIST\n"));
    554 
     630                int rc = SharedClipboardWinClearAndAnnounceFormats(pWinCtx, fFormats, hwnd);
     631#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     632                if (   RT_SUCCESS(rc)
     633                    && fFormats & VBOX_SHCL_FMT_URI_LIST)
     634                {
    555635                    /*
    556636                     * Creating and starting the actual transfer will be done in vbglR3ClipboardTransferStart() as soon
     
    558638                     * Transfers always are controlled and initiated on the host side!
    559639                     *
    560                      * So don't announce the transfer to the OS here yet. Don't touch the clipboard in any way here; otherwise
    561                      * this will trigger a WM_DRAWCLIPBOARD or friends, which will result in fun bugs coming up.
     640                     * What we need to do here, however, is, that we create our IDataObject implementation and push it to the
     641                     * Windows clibpoard. That way Windows will recognize that there is a data transfer "in flight".
    562642                     */
    563                 }
    564                 else
    565                 {
    566 #endif
    567                     SharedClipboardWinClearAndAnnounceFormats(pWinCtx, fFormats, hwnd);
    568 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     643                    SHCLCALLBACKS Callbacks;
     644                    RT_ZERO(Callbacks);
     645                    Callbacks.pfnOnRequestDataFromSource = vbtrShClRequestDataFromSourceCallback;
     646
     647                    rc = SharedClipboardWinTransferCreateAndSetDataObject(pWinCtx, pCtx, &Callbacks);
    569648                }
    570649#endif
     
    575654        }
    576655
    577         case SHCL_WIN_WM_READ_DATA:
     656        case SHCL_WIN_WM_READ_DATA: /* Host wants to read clipboard data from the guest. */
    578657        {
    579658            /* Send data in the specified format to the host. */
     
    585664
    586665            LogFlowFunc(("SHCL_WIN_WM_READ_DATA: fFormat=%#x\n", fFormat));
    587 
     666#ifdef LOG_ENABLED
     667            char *pszFmts = ShClFormatsToStrA(fFormat);
     668            AssertPtrReturn(pszFmts, 0);
     669            LogRel(("Shared Clipboard: Sending data to host as '%s'\n", pszFmts));
     670            RTStrFree(pszFmts);
     671#endif
    588672            int rc = SharedClipboardWinOpen(hwnd);
    589673            HANDLE hClip = NULL;
     
    673757        }
    674758
    675 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    676         case SHCL_WIN_WM_TRANSFER_START:
    677         {
    678             LogFunc(("SHCL_WIN_WM_TRANSFER_START\n"));
    679 
    680             PSHCLTRANSFER pTransfer  = (PSHCLTRANSFER)wParam;
    681             AssertPtr(pTransfer);
    682 
    683             const SHCLEVENTID idEvent = (SHCLEVENTID)lParam;
    684 
    685             Assert(ShClTransferGetSource(pTransfer) == SHCLSOURCE_REMOTE); /* Sanity. */
    686 
    687             int rcTransfer = SharedClipboardWinTransferCreate(pWinCtx, pTransfer);
    688 
    689             PSHCLEVENTPAYLOAD pPayload = NULL;
    690             int rc = ShClPayloadAlloc(idEvent, &rcTransfer, sizeof(rcTransfer), &pPayload);
    691             if (RT_SUCCESS(rc))
    692             {
    693                 rc = ShClEventSignal(&pTransfer->Events, idEvent, pPayload);
    694                 if (RT_FAILURE(rc))
    695                     ShClPayloadFree(pPayload);
    696             }
    697 
    698             break;
    699         }
    700 #endif
    701759        case WM_DESTROY:
    702760        {
     
    726784}
    727785
    728 static LRESULT CALLBACK vboxClipboardWinWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    729 
    730 static int vboxClipboardCreateWindow(PSHCLCONTEXT pCtx)
     786static LRESULT CALLBACK vbtrShClWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
     787
     788static int vbtrShClCreateWindow(PSHCLCONTEXT pCtx)
    731789{
    732790    AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
     
    747805    {
    748806        wc.style         = CS_NOCLOSE;
    749         wc.lpfnWndProc   = vboxClipboardWinWndProc;
     807        wc.lpfnWndProc   = vbtrShClWndProc;
    750808        wc.hInstance     = pCtx->pEnv->hInstance;
    751809        wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
     
    788846}
    789847
    790 static DECLCALLBACK(int) vboxClipboardWindowThread(RTTHREAD hThread, void *pvUser)
     848static DECLCALLBACK(int) vbtrShClWindowThread(RTTHREAD hThread, void *pvUser)
    791849{
    792850    PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser;
     
    802860    else
    803861        LogRel(("Shared Clipboard: Initialized OLE in window thread\n"));
    804 #endif
    805 
    806     int rc = vboxClipboardCreateWindow(pCtx);
     862#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
     863
     864    int rc = vbtrShClCreateWindow(pCtx);
    807865    if (RT_FAILURE(rc))
    808866    {
     
    855913}
    856914
    857 static void vboxClipboardDestroy(PSHCLCONTEXT pCtx)
    858 {
     915static LRESULT CALLBACK vbtrShClWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
     916{
     917    PSHCLCONTEXT pCtx = &g_Ctx; /** @todo r=andy Make pCtx available through SetWindowLongPtr() / GWL_USERDATA. */
     918    AssertPtr(pCtx);
     919
     920    /* Forward with proper context. */
     921    return vbtrShClWndProcWorker(pCtx, hWnd, uMsg, wParam, lParam);
     922}
     923
     924DECLCALLBACK(int) vbtrShClInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
     925{
     926    LogFlowFuncEnter();
     927
     928    PSHCLCONTEXT pCtx = &g_Ctx; /* Only one instance for now. */
     929    AssertPtr(pCtx);
     930
     931    if (pCtx->pEnv)
     932    {
     933        /* Clipboard was already initialized. 2 or more instances are not supported. */
     934        return VERR_NOT_SUPPORTED;
     935    }
     936
     937    if (VbglR3AutoLogonIsRemoteSession())
     938    {
     939        /* Do not use clipboard for remote sessions. */
     940        LogRel(("Shared Clipboard: Clipboard has been disabled for a remote session\n"));
     941        return VERR_NOT_SUPPORTED;
     942    }
     943
     944    pCtx->pEnv      = pEnv;
     945    pCtx->hThread   = NIL_RTTHREAD;
     946    pCtx->fStarted  = false;
     947    pCtx->fShutdown = false;
     948
     949    int rc = RTReqQueueCreate(&pCtx->Win.hReqQ);
     950    AssertRCReturn(rc, rc);
     951
     952    rc = SharedClipboardWinCtxInit(&pCtx->Win);
     953    if (RT_SUCCESS(rc))
     954        rc = VbglR3ClipboardConnectEx(&pCtx->CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
     955    if (RT_SUCCESS(rc))
     956    {
     957#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     958        rc = ShClTransferCtxInit(&pCtx->TransferCtx);
     959#endif
     960        if (RT_SUCCESS(rc))
     961        {
     962            /* Message pump thread for our proxy window. */
     963            rc = RTThreadCreate(&pCtx->hThread, vbtrShClWindowThread, pCtx /* pvUser */,
     964                                0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
     965                                "shclwnd");
     966            if (RT_SUCCESS(rc))
     967            {
     968                int rc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */);
     969                AssertRC(rc2);
     970
     971                if (!pCtx->fStarted) /* Did the thread fail to start? */
     972                    rc = VERR_NOT_SUPPORTED; /* Report back Shared Clipboard as not being supported. */
     973            }
     974        }
     975
     976        if (RT_SUCCESS(rc))
     977        {
     978            *ppInstance = pCtx;
     979        }
     980        else
     981            VbglR3ClipboardDisconnectEx(&pCtx->CmdCtx);
     982    }
     983
     984    if (RT_FAILURE(rc))
     985        LogRel(("Shared Clipboard: Unable to initialize, rc=%Rrc\n", rc));
     986
     987    LogFlowFuncLeaveRC(rc);
     988    return rc;
     989}
     990
     991DECLCALLBACK(int) vbtrShClWorker(void *pInstance, bool volatile *pfShutdown)
     992{
     993    AssertPtr(pInstance);
     994    LogFlowFunc(("pInstance=%p\n", pInstance));
     995
     996    /*
     997     * Tell the control thread that it can continue
     998     * spawning services.
     999     */
     1000    RTThreadUserSignal(RTThreadSelf());
     1001
     1002    const PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
     1003    AssertPtr(pCtx);
     1004
     1005    const PSHCLWINCTX pWinCtx = &pCtx->Win;
     1006
     1007    LogRel2(("Shared Clipboard: Worker loop running\n"));
     1008
     1009#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     1010    HRESULT hr = OleInitialize(NULL);
     1011    if (FAILED(hr))
     1012    {
     1013        LogRel(("Shared Clipboard: Initializing OLE in worker thread failed (%Rhrc) -- file transfers unavailable\n", hr));
     1014        /* Not critical, the rest of the clipboard might work. */
     1015    }
     1016    else
     1017        LogRel(("Shared Clipboard: Initialized OLE in worker thread\n"));
     1018
     1019    /*
     1020     * Init callbacks.
     1021     * Those will be registered within VbglR3 when a new transfer gets initialized.
     1022     */
     1023    RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
     1024
     1025    pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
     1026    pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
     1027
     1028    pCtx->CmdCtx.Transfers.Callbacks.pfnOnStarted     = vbtrShClTransferStartedCallback;
     1029    pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted   = vbtrShClTransferCompletedCallback;
     1030    pCtx->CmdCtx.Transfers.Callbacks.pfnOnError       = vbtrShClTransferErrorCallback;
     1031#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
     1032
     1033    int rc;
     1034
     1035    /* The thread waits for incoming messages from the host. */
     1036    PVBGLR3CLIPBOARDEVENT pEvent = NULL;
     1037    for (;;)
     1038    {
     1039        LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
     1040                     pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
     1041
     1042        if (!pEvent)
     1043            pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
     1044        AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
     1045
     1046        uint32_t idMsg  = 0;
     1047        uint32_t cParms = 0;
     1048        rc = VbglR3ClipboardMsgPeek(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
     1049        if (RT_SUCCESS(rc))
     1050        {
     1051#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     1052            rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
     1053#else
     1054            rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
     1055#endif
     1056        }
     1057        else if (rc == VERR_TRY_AGAIN) /* No new message (yet). */
     1058        {
     1059            RTReqQueueProcess(pCtx->Win.hReqQ, RT_MS_1SEC);
     1060            continue;
     1061        }
     1062
     1063        if (RT_FAILURE(rc))
     1064        {
     1065            LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
     1066
     1067            VbglR3ClipboardEventFree(pEvent);
     1068            pEvent = NULL;
     1069
     1070            if (*pfShutdown)
     1071                break;
     1072
     1073            /* Wait a bit before retrying. */
     1074            RTThreadSleep(1000);
     1075            continue;
     1076        }
     1077        else
     1078        {
     1079            AssertPtr(pEvent);
     1080            LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
     1081
     1082            switch (pEvent->enmType)
     1083            {
     1084                case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
     1085                {
     1086                    /* The host has announced available clipboard formats.
     1087                     * Forward the information to the window, so it can later
     1088                     * respond to WM_RENDERFORMAT message. */
     1089                    ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_REPORT_FORMATS,
     1090                                  0 /* wParam */, (LPARAM)pEvent /* lParam */);
     1091
     1092                    pEvent = NULL; /* Consume pointer. */
     1093                    break;
     1094                }
     1095
     1096                case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
     1097                {
     1098                    /* The host needs data in the specified format. */
     1099                    ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_READ_DATA,
     1100                                  0 /* wParam */, (LPARAM)pEvent /* lParam */);
     1101
     1102                    pEvent = NULL; /* Consume pointer. */
     1103                    break;
     1104                }
     1105
     1106                case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
     1107                {
     1108                    LogRel2(("Shared Clipboard: Host requested termination\n"));
     1109                    ASMAtomicXchgBool(pfShutdown, true);
     1110                    break;
     1111                }
     1112
     1113#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     1114                case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
     1115                {
     1116                    /* Nothing to do here. */
     1117                    rc = VINF_SUCCESS;
     1118                    break;
     1119                }
     1120#endif
     1121                case VBGLR3CLIPBOARDEVENTTYPE_NONE:
     1122                {
     1123                    /* Nothing to do here. */
     1124                    rc = VINF_SUCCESS;
     1125                    break;
     1126                }
     1127
     1128                default:
     1129                {
     1130                    AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
     1131                }
     1132            }
     1133
     1134            if (pEvent)
     1135            {
     1136                VbglR3ClipboardEventFree(pEvent);
     1137                pEvent = NULL;
     1138            }
     1139        }
     1140
     1141        if (*pfShutdown)
     1142            break;
     1143    }
     1144
     1145    LogRel2(("Shared Clipboard: Worker loop ended\n"));
     1146
     1147#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     1148    OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
     1149    OleUninitialize();
     1150#endif
     1151
     1152    LogFlowFuncLeaveRC(rc);
     1153    return rc;
     1154}
     1155
     1156DECLCALLBACK(int) vbtrShClStop(void *pInstance)
     1157{
     1158    AssertPtrReturn(pInstance, VERR_INVALID_POINTER);
     1159
     1160    LogFunc(("Stopping pInstance=%p\n", pInstance));
     1161
     1162    PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
     1163    AssertPtr(pCtx);
     1164
     1165    /* Set shutdown indicator. */
     1166    ASMAtomicWriteBool(&pCtx->fShutdown, true);
     1167
     1168    /* Let our clipboard know that we're going to shut down. */
     1169    PostMessage(pCtx->Win.hWnd, WM_QUIT, 0, 0);
     1170
     1171    /* Disconnect from the host service.
     1172     * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */
     1173    VbglR3ClipboardDisconnect(pCtx->CmdCtx.idClient);
     1174    pCtx->CmdCtx.idClient = 0;
     1175
     1176    LogFlowFuncLeaveRC(VINF_SUCCESS);
     1177    return VINF_SUCCESS;
     1178}
     1179
     1180DECLCALLBACK(void) vbtrShClDestroy(void *pInstance)
     1181{
     1182    AssertPtrReturnVoid(pInstance);
     1183
     1184    PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
    8591185    AssertPtrReturnVoid(pCtx);
     1186
     1187    /* Make sure that we are disconnected. */
     1188    Assert(pCtx->CmdCtx.idClient == 0);
    8601189
    8611190    LogFlowFunc(("pCtx=%p\n", pCtx));
     
    8841213    SharedClipboardWinCtxDestroy(&pCtx->Win);
    8851214
     1215#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
     1216    ShClTransferCtxDestroy(&pCtx->TransferCtx);
     1217#endif
     1218
     1219    RTReqQueueDestroy(pCtx->Win.hReqQ);
     1220
    8861221    LogRel2(("Shared Clipboard: Destroyed\n"));
    887 }
    888 
    889 static LRESULT CALLBACK vboxClipboardWinWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    890 {
    891     PSHCLCONTEXT pCtx = &g_Ctx; /** @todo r=andy Make pCtx available through SetWindowLongPtr() / GWL_USERDATA. */
    892     AssertPtr(pCtx);
    893 
    894     /* Forward with proper context. */
    895     return vboxClipboardWinProcessMsg(pCtx, hWnd, uMsg, wParam, lParam);
    896 }
    897 
    898 DECLCALLBACK(int) VBoxShClInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
    899 {
    900     LogFlowFuncEnter();
    901 
    902     PSHCLCONTEXT pCtx = &g_Ctx; /* Only one instance for now. */
    903     AssertPtr(pCtx);
    904 
    905     if (pCtx->pEnv)
    906     {
    907         /* Clipboard was already initialized. 2 or more instances are not supported. */
    908         return VERR_NOT_SUPPORTED;
    909     }
    910 
    911     if (VbglR3AutoLogonIsRemoteSession())
    912     {
    913         /* Do not use clipboard for remote sessions. */
    914         LogRel(("Shared Clipboard: Clipboard has been disabled for a remote session\n"));
    915         return VERR_NOT_SUPPORTED;
    916     }
    917 
    918     pCtx->pEnv      = pEnv;
    919     pCtx->hThread   = NIL_RTTHREAD;
    920     pCtx->fStarted  = false;
    921     pCtx->fShutdown = false;
    922 
    923     int rc = VINF_SUCCESS;
    924 
    925 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    926     /*
    927      * Set callbacks.
    928      * Those will be registered within VbglR3 when a new transfer gets initialized.
    929      */
    930     RT_ZERO(pCtx->CmdCtx.Transfers.Callbacks);
    931 
    932     pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */
    933     pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT);
    934 
    935     pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialize = vboxClipboardOnTransferInitCallback;
    936     pCtx->CmdCtx.Transfers.Callbacks.pfnOnStart      = vboxClipboardOnTransferStartCallback;
    937     pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted  = vboxClipboardOnTransferCompletedCallback;
    938     pCtx->CmdCtx.Transfers.Callbacks.pfnOnError      = vboxClipboardOnTransferErrorCallback;
    939 #endif
    940 
    941     if (RT_SUCCESS(rc))
    942     {
    943         rc = SharedClipboardWinCtxInit(&pCtx->Win);
    944         if (RT_SUCCESS(rc))
    945             rc = VbglR3ClipboardConnectEx(&pCtx->CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
    946         if (RT_SUCCESS(rc))
    947         {
    948 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    949             rc = ShClTransferCtxInit(&pCtx->TransferCtx);
    950 #endif
    951             if (RT_SUCCESS(rc))
    952             {
    953                 /* Message pump thread for our proxy window. */
    954                 rc = RTThreadCreate(&pCtx->hThread, vboxClipboardWindowThread, pCtx /* pvUser */,
    955                                     0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
    956                                     "shclwnd");
    957                 if (RT_SUCCESS(rc))
    958                 {
    959                     int rc2 = RTThreadUserWait(pCtx->hThread, 30 * 1000 /* Timeout in ms */);
    960                     AssertRC(rc2);
    961 
    962                     if (!pCtx->fStarted) /* Did the thread fail to start? */
    963                         rc = VERR_NOT_SUPPORTED; /* Report back Shared Clipboard as not being supported. */
    964                 }
    965             }
    966 
    967             if (RT_SUCCESS(rc))
    968             {
    969                 *ppInstance = pCtx;
    970             }
    971             else
    972                 VbglR3ClipboardDisconnectEx(&pCtx->CmdCtx);
    973         }
    974     }
    975 
    976     if (RT_FAILURE(rc))
    977         LogRel(("Shared Clipboard: Unable to initialize, rc=%Rrc\n", rc));
    978 
    979     LogFlowFuncLeaveRC(rc);
    980     return rc;
    981 }
    982 
    983 DECLCALLBACK(int) VBoxShClWorker(void *pInstance, bool volatile *pfShutdown)
    984 {
    985     AssertPtr(pInstance);
    986     LogFlowFunc(("pInstance=%p\n", pInstance));
    987 
    988     /*
    989      * Tell the control thread that it can continue
    990      * spawning services.
    991      */
    992     RTThreadUserSignal(RTThreadSelf());
    993 
    994     const PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
    995     AssertPtr(pCtx);
    996 
    997     const PSHCLWINCTX pWinCtx = &pCtx->Win;
    998 
    999     LogRel2(("Shared Clipboard: Worker loop running\n"));
    1000 
    1001 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    1002     HRESULT hr = OleInitialize(NULL);
    1003     if (FAILED(hr))
    1004     {
    1005         LogRel(("Shared Clipboard: Initializing OLE in worker thread failed (%Rhrc) -- file transfers unavailable\n", hr));
    1006         /* Not critical, the rest of the clipboard might work. */
    1007     }
    1008     else
    1009         LogRel(("Shared Clipboard: Initialized OLE in worker thread\n"));
    1010 #endif
    1011 
    1012     int rc;
    1013 
    1014     /* The thread waits for incoming messages from the host. */
    1015     for (;;)
    1016     {
    1017         LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
    1018                      pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
    1019 
    1020         PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
    1021         AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
    1022 
    1023         uint32_t idMsg  = 0;
    1024         uint32_t cParms = 0;
    1025         rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
    1026         if (RT_SUCCESS(rc))
    1027         {
    1028 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    1029             rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
    1030 #else
    1031             rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
    1032 #endif
    1033         }
    1034 
    1035         if (RT_FAILURE(rc))
    1036         {
    1037             LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
    1038 
    1039             VbglR3ClipboardEventFree(pEvent);
    1040             pEvent = NULL;
    1041 
    1042             if (*pfShutdown)
    1043                 break;
    1044 
    1045             /* Wait a bit before retrying. */
    1046             RTThreadSleep(1000);
    1047             continue;
    1048         }
    1049         else
    1050         {
    1051             AssertPtr(pEvent);
    1052             LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
    1053 
    1054             switch (pEvent->enmType)
    1055             {
    1056                 case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
    1057                 {
    1058                     /* The host has announced available clipboard formats.
    1059                      * Forward the information to the window, so it can later
    1060                      * respond to WM_RENDERFORMAT message. */
    1061                     ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_REPORT_FORMATS,
    1062                                   0 /* wParam */, (LPARAM)pEvent /* lParam */);
    1063 
    1064                     pEvent = NULL; /* Consume pointer. */
    1065                     break;
    1066                 }
    1067 
    1068                 case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
    1069                 {
    1070                     /* The host needs data in the specified format. */
    1071                     ::PostMessage(pWinCtx->hWnd, SHCL_WIN_WM_READ_DATA,
    1072                                   0 /* wParam */, (LPARAM)pEvent /* lParam */);
    1073 
    1074                     pEvent = NULL; /* Consume pointer. */
    1075                     break;
    1076                 }
    1077 
    1078                 case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
    1079                 {
    1080                     LogRel2(("Shared Clipboard: Host requested termination\n"));
    1081                     ASMAtomicXchgBool(pfShutdown, true);
    1082                     break;
    1083                 }
    1084 
    1085 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    1086                 case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
    1087                 {
    1088                     /* Nothing to do here. */
    1089                     rc = VINF_SUCCESS;
    1090                     break;
    1091                 }
    1092 #endif
    1093                 case VBGLR3CLIPBOARDEVENTTYPE_NONE:
    1094                 {
    1095                     /* Nothing to do here. */
    1096                     rc = VINF_SUCCESS;
    1097                     break;
    1098                 }
    1099 
    1100                 default:
    1101                 {
    1102                     AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
    1103                 }
    1104             }
    1105 
    1106             if (pEvent)
    1107             {
    1108                 VbglR3ClipboardEventFree(pEvent);
    1109                 pEvent = NULL;
    1110             }
    1111         }
    1112 
    1113         if (*pfShutdown)
    1114             break;
    1115     }
    1116 
    1117     LogRel2(("Shared Clipboard: Worker loop ended\n"));
    1118 
    1119 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    1120     OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */
    1121     OleUninitialize();
    1122 #endif
    1123 
    1124     LogFlowFuncLeaveRC(rc);
    1125     return rc;
    1126 }
    1127 
    1128 DECLCALLBACK(int) VBoxShClStop(void *pInstance)
    1129 {
    1130     AssertPtrReturn(pInstance, VERR_INVALID_POINTER);
    1131 
    1132     LogFunc(("Stopping pInstance=%p\n", pInstance));
    1133 
    1134     PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
    1135     AssertPtr(pCtx);
    1136 
    1137     /* Set shutdown indicator. */
    1138     ASMAtomicWriteBool(&pCtx->fShutdown, true);
    1139 
    1140     /* Let our clipboard know that we're going to shut down. */
    1141     PostMessage(pCtx->Win.hWnd, WM_QUIT, 0, 0);
    1142 
    1143     /* Disconnect from the host service.
    1144      * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */
    1145     VbglR3ClipboardDisconnect(pCtx->CmdCtx.idClient);
    1146     pCtx->CmdCtx.idClient = 0;
    1147 
    1148     LogFlowFuncLeaveRC(VINF_SUCCESS);
    1149     return VINF_SUCCESS;
    1150 }
    1151 
    1152 DECLCALLBACK(void) VBoxShClDestroy(void *pInstance)
    1153 {
    1154     AssertPtrReturnVoid(pInstance);
    1155 
    1156     PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pInstance;
    1157     AssertPtr(pCtx);
    1158 
    1159     /* Make sure that we are disconnected. */
    1160     Assert(pCtx->CmdCtx.idClient == 0);
    1161 
    1162     vboxClipboardDestroy(pCtx);
    1163 
    1164 #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
    1165     ShClTransferCtxDestroy(&pCtx->TransferCtx);
    1166 #endif
    11671222
    11681223    return;
     
    11791234    "Shared Clipboard",
    11801235    /* methods */
    1181     VBoxShClInit,
    1182     VBoxShClWorker,
    1183     VBoxShClStop,
    1184     VBoxShClDestroy
     1236    vbtrShClInit,
     1237    vbtrShClWorker,
     1238    vbtrShClStop,
     1239    vbtrShClDestroy
    11851240};
  • trunk/src/VBox/Additions/WINNT/VBoxTray/VBoxTray.cpp

    r99739 r100204  
    9898static VBOXSERVICEINFO g_aServices[] =
    9999{
    100     {&g_SvcDescDnD,      NIL_RTTHREAD, NULL, false, false, false, false, true }
     100    { &g_SvcDescClipboard,      NIL_RTTHREAD, NULL, false, false, false, false, true }
    101101};
    102102#else
     
    533533        RTLogSetDefaultInstance(g_pLoggerRelease);
    534534
    535         const char *apszGroups[] = { "all", "guest_dnd" }; /* All groups we want to enable logging for VBoxTray. */
     535        /* All groups we want to enable logging for VBoxTray. */
     536        const char *apszGroups[] = { "guest_dnd", "shared_clipboard" };
    536537        char        szGroupSettings[_1K];
    537538
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