/* $Id: tstClipboardGH-X11.cpp $ */ /** @file * Shared Clipboard guest/host X11 code test cases. */ /* * Copyright (C) 2011-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Externals * *********************************************************************************************************************************/ extern SHCLX11FMTTABLE g_aFormats[]; extern void clipUpdateX11Targets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *pTargets, size_t cTargets); extern void clipReportEmpty(PSHCLX11CTX pCtx); extern void clipConvertDataFromX11Worker(void *pClient, void *pvSrc, unsigned cbSrc); extern SHCLX11FMTIDX clipGetTextFormatFromTargets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *pTargets, size_t cTargets); extern SHCLX11FMT clipRealFormatForX11Format(SHCLX11FMTIDX uFmtIdx); extern Atom clipGetAtom(PSHCLX11CTX pCtx, const char *pcszName); extern void clipQueryX11Targets(PSHCLX11CTX pCtx); extern size_t clipReportMaxX11Formats(void); /********************************************************************************************************************************* * Internal prototypes * *********************************************************************************************************************************/ static SHCLX11FMTIDX tstClipFindX11FormatByAtomText(const char *pcszAtom); /********************************************************************************************************************************* * Prototypes, used for testcases by clipboard-x11.cpp * *********************************************************************************************************************************/ void tstRequestTargets(SHCLX11CTX* pCtx); void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure); void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data); /********************************************************************************************************************************* * Own callback implementations * *********************************************************************************************************************************/ extern DECLCALLBACK(void) clipQueryX11TargetsCallback(Widget widget, XtPointer pClient, Atom * /* selection */, Atom *atomType, XtPointer pValue, long unsigned int *pcLen, int *piFormat); /********************************************************************************************************************************* * Defines * *********************************************************************************************************************************/ #define TESTCASE_WIDGET_ID (Widget)0xffff /* For the purpose of the test case, we just execute the procedure to be * scheduled, as we are running single threaded. */ void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data) { proc(client_data, NULL); } /* The data in the simulated VBox clipboard. */ static int g_tst_rcDataVBox = VINF_SUCCESS; static void *g_tst_pvDataVBox = NULL; static uint32_t g_tst_cbDataVBox = 0; /* Set empty data in the simulated VBox clipboard. */ static void tstClipEmptyVBox(PSHCLX11CTX pCtx, int retval) { g_tst_rcDataVBox = retval; RTMemFree(g_tst_pvDataVBox); g_tst_pvDataVBox = NULL; g_tst_cbDataVBox = 0; ShClX11ReportFormatsToX11(pCtx, 0); } /* Set the data in the simulated VBox clipboard. */ static int tstClipSetVBoxUtf16(PSHCLX11CTX pCtx, int retval, const char *pcszData, size_t cb) { PRTUTF16 pwszData = NULL; size_t cwData = 0; int rc = RTStrToUtf16Ex(pcszData, RTSTR_MAX, &pwszData, 0, &cwData); if (RT_FAILURE(rc)) return rc; AssertReturn(cb <= cwData * 2 + 2, VERR_BUFFER_OVERFLOW); void *pv = RTMemDup(pwszData, cb); RTUtf16Free(pwszData); if (pv == NULL) return VERR_NO_MEMORY; if (g_tst_pvDataVBox) RTMemFree(g_tst_pvDataVBox); g_tst_rcDataVBox = retval; g_tst_pvDataVBox = pv; g_tst_cbDataVBox = cb; ShClX11ReportFormatsToX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT); return VINF_SUCCESS; } Display *XtDisplay(Widget w) { NOREF(w); return (Display *) 0xffff; } void XtAppSetExitFlag(XtAppContext app_context) { NOREF(app_context); } void XtDestroyWidget(Widget w) { NOREF(w); } XtAppContext XtCreateApplicationContext(void) { return (XtAppContext)0xffff; } void XtDestroyApplicationContext(XtAppContext app_context) { NOREF(app_context); } void XtToolkitInitialize(void) {} Boolean XtToolkitThreadInitialize(void) { return True; } Display *XtOpenDisplay(XtAppContext app_context, _Xconst _XtString display_string, _Xconst _XtString application_name, _Xconst _XtString application_class, XrmOptionDescRec *options, Cardinal num_options, int *argc, char **argv) { RT_NOREF8(app_context, display_string, application_name, application_class, options, num_options, argc, argv); return (Display *)0xffff; } Widget XtVaAppCreateShell(_Xconst _XtString application_name, _Xconst _XtString application_class, WidgetClass widget_class, Display *display, ...) { RT_NOREF(application_name, application_class, widget_class, display); return TESTCASE_WIDGET_ID; } void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) { RT_NOREF(widget, mapped_when_managed); } void XtRealizeWidget(Widget widget) { NOREF(widget); } XtInputId XtAppAddInput(XtAppContext app_context, int source, XtPointer condition, XtInputCallbackProc proc, XtPointer closure) { RT_NOREF(app_context, source, condition, proc, closure); return 0xffff; } /* Atoms we need other than the formats we support. */ static const char *g_tst_apszSupAtoms[] = { "PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP" }; /* This just looks for the atom names in a couple of tables and returns an * index with an offset added. */ Atom XInternAtom(Display *, const char *pcsz, int) { Atom atom = 0; size_t const cFormats = clipReportMaxX11Formats(); size_t i; for (i = 0; i < cFormats; ++i) { if (!strcmp(pcsz, g_aFormats[i].pcszAtom)) atom = (Atom) (i + 0x1000); } for (i = 0; i < RT_ELEMENTS(g_tst_apszSupAtoms); ++i) if (!strcmp(pcsz, g_tst_apszSupAtoms[i])) atom = (Atom) (i + 0x2000); Assert(atom); /* Have we missed any atoms? */ return atom; } /* Take a request for the targets we are currently offering. */ static SHCLX11FMTIDX g_tst_aSelTargetsIdx[10] = { 0 }; static size_t g_tst_cTargets = 0; void tstRequestTargets(SHCLX11CTX* pCtx) { clipUpdateX11Targets(pCtx, g_tst_aSelTargetsIdx, g_tst_cTargets); } /* The current values of the X selection, which will be returned to the * XtGetSelectionValue callback. */ static Atom g_tst_atmSelType = 0; static const void *g_tst_pSelData = NULL; static unsigned long g_tst_cSelData = 0; static int g_tst_selFormat = 0; void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure) { RT_NOREF(pCtx); unsigned long count = 0; int format = 0; if (target != g_tst_aSelTargetsIdx[0]) { clipConvertDataFromX11Worker(closure, NULL, 0); /* Could not convert to target. */ return; } void *pValue = NULL; pValue = g_tst_pSelData ? RTMemDup(g_tst_pSelData, g_tst_cSelData) : NULL; count = g_tst_pSelData ? g_tst_cSelData : 0; format = g_tst_selFormat; if (!pValue) { count = 0; format = 0; } clipConvertDataFromX11Worker(closure, pValue, count * format / 8); if (pValue) RTMemFree(pValue); } /* The formats currently on offer from X11 via the shared clipboard. */ static uint32_t g_tst_uX11Formats = 0; static uint32_t tstClipQueryFormats(void) { return g_tst_uX11Formats; } static void tstClipInvalidateFormats(void) { g_tst_uX11Formats = ~0; } /* Does our clipboard code currently own the selection? */ static bool g_tst_fOwnsSel = false; /* The procedure that is called when we should convert the selection to a * given format. */ static XtConvertSelectionProc g_tst_pfnSelConvert = NULL; /* The procedure which is called when we lose the selection. */ static XtLoseSelectionProc g_tst_pfnSelLose = NULL; /* The procedure which is called when the selection transfer has completed. */ static XtSelectionDoneProc g_tst_pfnSelDone = NULL; Boolean XtOwnSelection(Widget widget, Atom selection, Time time, XtConvertSelectionProc convert, XtLoseSelectionProc lose, XtSelectionDoneProc done) { RT_NOREF(widget, time); if (selection != XInternAtom(NULL, "CLIPBOARD", 0)) return True; /* We don't really care about this. */ g_tst_fOwnsSel = true; /* Always succeed. */ g_tst_pfnSelConvert = convert; g_tst_pfnSelLose = lose; g_tst_pfnSelDone = done; return True; } void XtDisownSelection(Widget widget, Atom selection, Time time) { RT_NOREF(widget, time, selection); g_tst_fOwnsSel = false; g_tst_pfnSelConvert = NULL; g_tst_pfnSelLose = NULL; g_tst_pfnSelDone = NULL; } /* Request the shared clipboard to convert its data to a given format. */ static bool tstClipConvertSelection(const char *pcszTarget, Atom *type, XtPointer *value, unsigned long *length, int *format) { Atom target = XInternAtom(NULL, pcszTarget, 0); if (target == 0) return false; /* Initialise all return values in case we make a quick exit. */ *type = XA_STRING; *value = NULL; *length = 0; *format = 0; if (!g_tst_fOwnsSel) return false; if (!g_tst_pfnSelConvert) return false; Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0); if (!g_tst_pfnSelConvert(TESTCASE_WIDGET_ID, &clipAtom, &target, type, value, length, format)) return false; if (g_tst_pfnSelDone) g_tst_pfnSelDone(TESTCASE_WIDGET_ID, &clipAtom, &target); return true; } /* Set the current X selection data */ static void tstClipSetSelectionValues(const char *pcszTarget, Atom type, const void *data, unsigned long count, int format) { Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0); g_tst_aSelTargetsIdx[0] = tstClipFindX11FormatByAtomText(pcszTarget); g_tst_cTargets = 1; g_tst_atmSelType = type; g_tst_pSelData = data; g_tst_cSelData = count; g_tst_selFormat = format; if (g_tst_pfnSelLose) g_tst_pfnSelLose(TESTCASE_WIDGET_ID, &clipAtom); g_tst_fOwnsSel = false; } static void tstClipSendTargetUpdate(PSHCLX11CTX pCtx) { clipQueryX11Targets(pCtx); } /* Configure if and how the X11 TARGETS clipboard target will fail. */ static void tstClipSetTargetsFailure(void) { g_tst_cTargets = 0; } char *XtMalloc(Cardinal size) { return (char *) RTMemAlloc(size); } void XtFree(char *ptr) { RTMemFree((void *)ptr); } char *XGetAtomName(Display *display, Atom atom) { RT_NOREF(display); const char *pcszName = NULL; if (atom < 0x1000) return NULL; if (0x1000 <= atom && atom < 0x2000) { unsigned index = atom - 0x1000; AssertReturn(index < clipReportMaxX11Formats(), NULL); pcszName = g_aFormats[index].pcszAtom; } else { unsigned index = atom - 0x2000; AssertReturn(index < RT_ELEMENTS(g_tst_apszSupAtoms), NULL); pcszName = g_tst_apszSupAtoms[index]; } return (char *)RTMemDup(pcszName, sizeof(pcszName) + 1); } int XFree(void *data) { RTMemFree(data); return 0; } void XFreeStringList(char **list) { if (list) RTMemFree(*list); RTMemFree(list); } #define TESTCASE_MAX_BUF_SIZE 256 static int g_tst_rcCompleted = VINF_SUCCESS; static int g_tst_cbCompleted = 0; static CLIPREADCBREQ *g_tst_pCompletedReq = NULL; static char g_tst_abCompletedBuf[TESTCASE_MAX_BUF_SIZE]; static DECLCALLBACK(int) tstShClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) { RT_NOREF(pCtx, pvUser); g_tst_uX11Formats = fFormats; return VINF_SUCCESS; } static DECLCALLBACK(int) tstShClOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) { RT_NOREF(pCtx, uFmt, pvUser); *pcb = g_tst_cbDataVBox; if (g_tst_pvDataVBox != NULL) { void *pv = RTMemDup(g_tst_pvDataVBox, g_tst_cbDataVBox); *ppv = pv; return pv != NULL ? g_tst_rcDataVBox : VERR_NO_MEMORY; } *ppv = NULL; return g_tst_rcDataVBox; } static DECLCALLBACK(int) tstShClOnSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser) { RT_NOREF(pCtx); PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser; if (cb <= TESTCASE_MAX_BUF_SIZE) { g_tst_rcCompleted = pData->rcCompletion; if (cb != 0) memcpy(g_tst_abCompletedBuf, pv, cb); } else g_tst_rcCompleted = VERR_BUFFER_OVERFLOW; g_tst_cbCompleted = cb; g_tst_pCompletedReq = pData->pReq; return VINF_SUCCESS; } /** * Looks up the X11 format matching a given X11 atom text. * * @returns the format on success, NIL_CLIPX11FORMAT on failure * @param pcszAtom Atom text to look up format for. */ static SHCLX11FMTIDX tstClipFindX11FormatByAtomText(const char *pcszAtom) { const size_t j = clipReportMaxX11Formats(); for (unsigned i = 0; i < j; ++i) { if (!strcmp(g_aFormats[i].pcszAtom, pcszAtom)) return i; } return NIL_CLIPX11FORMAT; } static bool tstClipTextFormatConversion(PSHCLX11CTX pCtx) { bool fSuccess = true; SHCLX11FMTIDX targets[2]; SHCLX11FMTIDX x11Format; targets[0] = tstClipFindX11FormatByAtomText("text/plain"); targets[1] = tstClipFindX11FormatByAtomText("image/bmp"); x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2); if (clipRealFormatForX11Format(x11Format) != SHCLX11FMT_TEXT) fSuccess = false; targets[0] = tstClipFindX11FormatByAtomText("UTF8_STRING"); targets[1] = tstClipFindX11FormatByAtomText("text/plain"); x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2); if (clipRealFormatForX11Format(x11Format) != SHCLX11FMT_UTF8) fSuccess = false; return fSuccess; } static void tstClipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb, CLIPREADCBREQ **ppReq) { *prc = g_tst_rcCompleted; *ppc = g_tst_abCompletedBuf; *pcb = g_tst_cbCompleted; *ppReq = g_tst_pCompletedReq; } static void tstStringFromX11(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszExp, int rcExp) { bool retval = true; tstClipSendTargetUpdate(pCtx); if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT) { RTTestFailed(hTest, "Wrong targets reported: %02X\n", tstClipQueryFormats()); } else { char *pc; CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL; ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq); int rc = VINF_SUCCESS; uint32_t cbActual = 0; tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet); if (rc != rcExp) RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n", rcExp, rc); else if (pReqRet != pReq) RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n", pReq, pReqRet); else if (RT_FAILURE(rcExp)) retval = true; else { RTUTF16 wcExp[TESTCASE_MAX_BUF_SIZE / 2]; RTUTF16 *pwcExp = wcExp; size_t cwc = 0; rc = RTStrToUtf16Ex(pcszExp, RTSTR_MAX, &pwcExp, RT_ELEMENTS(wcExp), &cwc); size_t cbExp = cwc * 2 + 2; AssertRC(rc); if (RT_SUCCESS(rc)) { if (cbActual != cbExp) { RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n", RT_MIN(TESTCASE_MAX_BUF_SIZE, cbActual), pc, cbActual, pcszExp, cbExp); } else { if (memcmp(pc, wcExp, cbExp) == 0) retval = true; else RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n", TESTCASE_MAX_BUF_SIZE, pc, pcszExp); } } } } if (!retval) RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n", pcszExp, rcExp); } static void tstLatin1FromX11(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszExp, int rcExp) { bool retval = false; tstClipSendTargetUpdate(pCtx); if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT) RTTestFailed(hTest, "Wrong targets reported: %02X\n", tstClipQueryFormats()); else { char *pc; CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL; ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq); int rc = VINF_SUCCESS; uint32_t cbActual = 0; tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet); if (rc != rcExp) RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n", rcExp, rc); else if (pReqRet != pReq) RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n", pReq, pReqRet); else if (RT_FAILURE(rcExp)) retval = true; else { RTUTF16 wcExp[TESTCASE_MAX_BUF_SIZE / 2]; //RTUTF16 *pwcExp = wcExp; - unused size_t cwc; for (cwc = 0; cwc == 0 || pcszExp[cwc - 1] != '\0'; ++cwc) wcExp[cwc] = pcszExp[cwc]; size_t cbExp = cwc * 2; if (cbActual != cbExp) { RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n", RT_MIN(TESTCASE_MAX_BUF_SIZE, cbActual), pc, cbActual, pcszExp, cbExp); } else { if (memcmp(pc, wcExp, cbExp) == 0) retval = true; else RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n", TESTCASE_MAX_BUF_SIZE, pc, pcszExp); } } } if (!retval) RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n", pcszExp, rcExp); } static void tstStringFromVBox(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget, Atom typeExp, const char *valueExp) { RT_NOREF(pCtx); bool retval = false; Atom type; XtPointer value = NULL; unsigned long length; int format; size_t lenExp = strlen(valueExp); if (tstClipConvertSelection(pcszTarget, &type, &value, &length, &format)) { if ( type != typeExp || length != lenExp || format != 8 || memcmp((const void *) value, (const void *)valueExp, lenExp)) { RTTestFailed(hTest, "Bad data: type %d, (expected %d), length %u, (expected %u), format %d (expected %d), value \"%.*s\" (expected \"%.*s\")\n", type, typeExp, length, lenExp, format, 8, RT_MIN(length, 20), value, RT_MIN(lenExp, 20), valueExp); } else retval = true; } else RTTestFailed(hTest, "Conversion failed\n"); XtFree((char *)value); if (!retval) RTTestFailureDetails(hTest, "Conversion to %s, expected \"%s\"\n", pcszTarget, valueExp); } static void tstNoX11(PSHCLX11CTX pCtx, const char *pcszTestCtx) { CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq; int rc = ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq); RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx)); } static void tstStringFromVBoxFailed(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget) { RT_NOREF(pCtx); Atom type; XtPointer value = NULL; unsigned long length; int format; RTTEST_CHECK_MSG(hTest, !tstClipConvertSelection(pcszTarget, &type, &value, &length, &format), (hTest, "Conversion to target %s, should have failed but didn't, returned type %d, length %u, format %d, value \"%.*s\"\n", pcszTarget, type, length, format, RT_MIN(length, 20), value)); XtFree((char *)value); } static void tstNoSelectionOwnership(PSHCLX11CTX pCtx, const char *pcszTestCtx) { RT_NOREF(pCtx); RTTESTI_CHECK_MSG(!g_tst_fOwnsSel, ("context: %s\n", pcszTestCtx)); } static void tstBadFormatRequestFromHost(RTTEST hTest, PSHCLX11CTX pCtx) { tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world", sizeof("hello world"), 8); tstClipSendTargetUpdate(pCtx); if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT) RTTestFailed(hTest, "Wrong targets reported: %02X\n", tstClipQueryFormats()); else { char *pc; CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL; ShClX11ReadDataFromX11(pCtx, 0xF000 /* vboxFormat */, pReq); /* Bad format. */ int rc = VINF_SUCCESS; uint32_t cbActual = 0; tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet); if (rc != VERR_NOT_IMPLEMENTED) RTTestFailed(hTest, "Wrong return code, expected VERR_NOT_IMPLEMENTED, got %Rrc\n", rc); tstClipSetSelectionValues("", XA_STRING, "", sizeof(""), 8); tstClipSendTargetUpdate(pCtx); if (tstClipQueryFormats() == VBOX_SHCL_FMT_UNICODETEXT) RTTestFailed(hTest, "Failed to report targets after bad host request.\n"); } } int main() { /* * Init the runtime, test and say hello. */ RTTEST hTest; int rc = RTTestInitAndCreate("tstClipboardGH-X11", &hTest); if (RT_FAILURE(rc)) return RTEXITCODE_FAILURE; RTTestBanner(hTest); /* * Run the tests. */ SHCLCALLBACKS Callbacks; RT_ZERO(Callbacks); Callbacks.pfnReportFormats = tstShClReportFormatsCallback; Callbacks.pfnOnRequestDataFromSource = tstShClOnRequestDataFromSourceCallback; Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDestCallback; SHCLX11CTX X11Ctx; rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */); AssertRCReturn(rc, RTEXITCODE_FAILURE); char *pc; uint32_t cbActual; CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL; /* UTF-8 from X11 */ RTTestSub(hTest, "reading UTF-8 from X11"); /* Simple test */ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world", sizeof("hello world"), 8); tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS); /* With an embedded carriage return */ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING, "hello\nworld", sizeof("hello\nworld"), 8); tstStringFromX11(hTest, &X11Ctx, "hello\r\nworld", VINF_SUCCESS); /* With an embedded CRLF */ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING, "hello\r\nworld", sizeof("hello\r\nworld"), 8); tstStringFromX11(hTest, &X11Ctx, "hello\r\r\nworld", VINF_SUCCESS); /* With an embedded LFCR */ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING, "hello\n\rworld", sizeof("hello\n\rworld"), 8); tstStringFromX11(hTest, &X11Ctx, "hello\r\n\rworld", VINF_SUCCESS); /* An empty string */ tstClipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "", sizeof(""), 8); tstStringFromX11(hTest, &X11Ctx, "", VINF_SUCCESS); /* With an embedded UTF-8 character. */ tstClipSetSelectionValues("STRING", XA_STRING, "100\xE2\x82\xAC" /* 100 Euro */, sizeof("100\xE2\x82\xAC"), 8); tstStringFromX11(hTest, &X11Ctx, "100\xE2\x82\xAC", VINF_SUCCESS); /* A non-zero-terminated string */ tstClipSetSelectionValues("TEXT", XA_STRING, "hello world", sizeof("hello world") - 1, 8); tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS); /* Latin1 from X11 */ RTTestSub(hTest, "reading Latin1 from X11"); /* Simple test */ tstClipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA", sizeof("Georges Dupr\xEA"), 8); tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA", VINF_SUCCESS); /* With an embedded carriage return */ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA", sizeof("Georges\nDupr\xEA"), 8); tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\nDupr\xEA", VINF_SUCCESS); /* With an embedded CRLF */ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA", sizeof("Georges\r\nDupr\xEA"), 8); tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS); /* With an embedded LFCR */ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA", sizeof("Georges\n\rDupr\xEA"), 8); tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS); /* A non-zero-terminated string */ tstClipSetSelectionValues("text/plain", XA_STRING, "Georges Dupr\xEA!", sizeof("Georges Dupr\xEA!") - 1, 8); tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA!", VINF_SUCCESS); /* * Unknown X11 format */ RTTestSub(hTest, "handling of an unknown X11 format"); tstClipInvalidateFormats(); tstClipSetSelectionValues("CLIPBOARD", XA_STRING, "Test", sizeof("Test"), 8); tstClipSendTargetUpdate(&X11Ctx); RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0, (hTest, "Failed to send a format update notification\n")); /* * Timeout from X11 */ RTTestSub(hTest, "X11 timeout"); tstClipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, NULL,0, 8); tstStringFromX11(hTest, &X11Ctx, "", VERR_NO_DATA); /* * No data in X11 clipboard */ RTTestSub(hTest, "a data request from an empty X11 clipboard"); tstClipSetSelectionValues("UTF8_STRING", XA_STRING, NULL, 0, 8); ShClX11ReadDataFromX11(&X11Ctx, VBOX_SHCL_FMT_UNICODETEXT, pReq); tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet); RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA, (hTest, "Returned %Rrc instead of VERR_NO_DATA\n", rc)); RTTEST_CHECK_MSG(hTest, pReqRet == pReq, (hTest, "Wrong returned request data, expected %p, got %p\n", pReq, pReqRet)); /* * Ensure that VBox is notified when we return the CB to X11 */ RTTestSub(hTest, "notification of switch to X11 clipboard"); tstClipInvalidateFormats(); clipReportEmpty(&X11Ctx); RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0, (hTest, "Failed to send a format update (release) notification\n")); /* * Request for an invalid VBox format from X11 */ RTTestSub(hTest, "a request for an invalid VBox format from X11"); /* Testing for 0xffff will go into handling VBOX_SHCL_FMT_UNICODETEXT, where we don't have * have any data at the moment so far, so this will return VERR_NO_DATA. */ ShClX11ReadDataFromX11(&X11Ctx, 0xffff /* vboxFormat */, pReq); tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet); RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA, (hTest, "Returned %Rrc instead of VERR_NO_DATA\n", rc)); RTTEST_CHECK_MSG(hTest, pReqRet == pReq, (hTest, "Wrong returned request data, expected %p, got %p\n", pReq, pReqRet)); /* * Targets failure from X11 */ RTTestSub(hTest, "X11 targets conversion failure"); tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world", sizeof("hello world"), 8); tstClipSetTargetsFailure(); Atom atom = XA_STRING; long unsigned int cLen = 0; int format = 8; clipQueryX11TargetsCallback(NULL, (XtPointer) &X11Ctx, NULL, &atom, NULL, &cLen, &format); RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0, (hTest, "Wrong targets reported: %02X\n", tstClipQueryFormats())); /* * X11 text format conversion */ RTTestSub(hTest, "handling of X11 selection targets"); RTTEST_CHECK_MSG(hTest, tstClipTextFormatConversion(&X11Ctx), (hTest, "failed to select the right X11 text formats\n")); /* * UTF-8 from VBox */ RTTestSub(hTest, "reading UTF-8 from VBox"); /* Simple test */ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world", sizeof("hello world") * 2); tstStringFromVBox(hTest, &X11Ctx, "UTF8_STRING", clipGetAtom(&X11Ctx, "UTF8_STRING"), "hello world"); /* With an embedded carriage return */ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\nworld", sizeof("hello\r\nworld") * 2); tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8", clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"), "hello\nworld"); /* With an embedded CRCRLF */ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\r\nworld", sizeof("hello\r\r\nworld") * 2); tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8", clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"), "hello\r\nworld"); /* With an embedded CRLFCR */ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\n\rworld", sizeof("hello\r\n\rworld") * 2); tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8", clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"), "hello\n\rworld"); /* An empty string */ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2); tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=utf-8", clipGetAtom(&X11Ctx, "text/plain;charset=utf-8"), ""); /* With an embedded UTF-8 character. */ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */, 10); tstStringFromVBox(hTest, &X11Ctx, "STRING", clipGetAtom(&X11Ctx, "STRING"), "100\xE2\x82\xAC"); /* A non-zero-terminated string */ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world", sizeof("hello world") * 2 - 2); tstStringFromVBox(hTest, &X11Ctx, "TEXT", clipGetAtom(&X11Ctx, "TEXT"), "hello world"); /* * Timeout from VBox */ RTTestSub(hTest, "reading from VBox with timeout"); tstClipEmptyVBox(&X11Ctx, VERR_TIMEOUT); tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING"); /* * No data in VBox clipboard */ RTTestSub(hTest, "an empty VBox clipboard"); tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8); tstClipEmptyVBox(&X11Ctx, VINF_SUCCESS); RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel, (hTest, "VBox grabbed the clipboard with no data and we ignored it\n")); tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING"); /* * An unknown VBox format */ RTTestSub(hTest, "reading an unknown VBox format"); tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8); tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2); ShClX11ReportFormatsToX11(&X11Ctx, 0xa0000); RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel, (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n")); tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING"); /* * VBox requests a bad format */ RTTestSub(hTest, "recovery from a bad format request"); tstBadFormatRequestFromHost(hTest, &X11Ctx); ShClX11Destroy(&X11Ctx); /* * Headless clipboard tests */ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, true /* fHeadless */); AssertRCReturn(rc, RTEXITCODE_FAILURE); /* Read from X11 */ RTTestSub(hTest, "reading from X11, headless clipboard"); /* Simple test */ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", sizeof("") * 2); tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world", sizeof("hello world"), 8); tstNoX11(&X11Ctx, "reading from X11, headless clipboard"); /* Read from VBox */ RTTestSub(hTest, "reading from VBox, headless clipboard"); /* Simple test */ tstClipEmptyVBox(&X11Ctx, VERR_WRONG_ORDER); tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8); tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world", sizeof("hello world") * 2); tstNoSelectionOwnership(&X11Ctx, "reading from VBox, headless clipboard"); ShClX11Destroy(&X11Ctx); return RTTestSummaryAndDestroy(hTest); }