summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp')
-rw-r--r--src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp1793
1 files changed, 1793 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp b/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp
new file mode 100644
index 00000000..4c1e10c5
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/clipboard/ClipUtil.cpp
@@ -0,0 +1,1793 @@
+/* $Id: ClipUtil.cpp $ */
+/** @file
+ * ClipUtil - Clipboard Utility
+ */
+
+/*
+ * Copyright (C) 2021-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 <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_OS2
+# define INCL_BASE
+# define INCL_PM
+# define INCL_ERRORS
+# include <os2.h>
+# undef RT_MAX
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/utf16.h>
+#include <iprt/zero.h>
+
+#ifdef RT_OS_DARWIN
+/** @todo */
+#elif defined(RT_OS_WINDOWS)
+# include <iprt/nt/nt-and-windows.h>
+#elif !defined(RT_OS_OS2)
+# include <X11/Xlib.h>
+# include <X11/Xatom.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) || defined(RT_OS_DARWIN)
+# undef MULTI_TARGET_CLIPBOARD
+# undef CU_X11
+#else
+# define MULTI_TARGET_CLIPBOARD
+# define CU_X11
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Clipboard format descriptor.
+ */
+typedef struct CLIPUTILFORMAT
+{
+ /** Format name. */
+ const char *pszName;
+
+#if defined(RT_OS_WINDOWS)
+ /** Windows integer format (CF_XXXX). */
+ UINT fFormat;
+ /** Windows string format name. */
+ const WCHAR *pwszFormat;
+
+#elif defined(RT_OS_OS2)
+ /** OS/2 integer format. */
+ ULONG fFormat;
+ /** OS/2 string format name. */
+ const char *pszFormat;
+
+#elif defined(RT_OS_DARWIN)
+ /** Native format (flavor). */
+ CFStringRef *hStrFormat;
+#else
+ /** The X11 atom for the format. */
+ Atom uAtom;
+ /** The X11 atom name if uAtom must be termined dynamically. */
+ const char *pszAtomName;
+ /** @todo X11 */
+#endif
+
+ /** Description. */
+ const char *pszDesc;
+ /** CLIPUTILFORMAT_F_XXX. */
+ uint32_t fFlags;
+} CLIPUTILFORMAT;
+/** Pointer to a clipobard format descriptor. */
+typedef CLIPUTILFORMAT const *PCCLIPUTILFORMAT;
+
+/** Convert to/from UTF-8. */
+#define CLIPUTILFORMAT_F_CONVERT_UTF8 RT_BIT_32(0)
+/** Ad hoc entry. */
+#define CLIPUTILFORMAT_F_AD_HOC RT_BIT_32(1)
+
+
+#ifdef MULTI_TARGET_CLIPBOARD
+/**
+ * Clipboard target descriptor.
+ */
+typedef struct CLIPUTILTARGET
+{
+ /** Target name. */
+ const char *pszName;
+ /** The X11 atom for the target. */
+ Atom uAtom;
+ /** The X11 atom name if uAtom must be termined dynamically. */
+ const char *pszAtomName;
+ /** Description. */
+ const char *pszDesc;
+} CLIPUTILTARGET;
+/** Pointer to clipboard target descriptor. */
+typedef CLIPUTILTARGET const *PCCLIPUTILTARGET;
+#endif /* MULTI_TARGET_CLIPBOARD */
+
+
+#ifdef RT_OS_OS2
+/** Header for Odin32 specific clipboard entries.
+ * (Used to get the correct size of the data.)
+ */
+typedef struct _Odin32ClipboardHeader
+{
+ /** Magic (CLIPHEADER_MAGIC) */
+ char achMagic[8];
+ /** Size of the following data.
+ * (The interpretation depends on the type.) */
+ unsigned cbData;
+ /** Odin32 format number. */
+ unsigned uFormat;
+} CLIPHEADER, *PCLIPHEADER;
+
+#define CLIPHEADER_MAGIC "Odin\1\0\1"
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Command line parameters */
+static const RTGETOPTDEF g_aCmdOptions[] =
+{
+ { "--list", 'l', RTGETOPT_REQ_NOTHING },
+ { "--get", 'g', RTGETOPT_REQ_STRING },
+ { "--get-file", 'G', RTGETOPT_REQ_STRING },
+ { "--put", 'p', RTGETOPT_REQ_STRING },
+ { "--put-file", 'P', RTGETOPT_REQ_STRING },
+ { "--check", 'c', RTGETOPT_REQ_STRING },
+ { "--check-file", 'C', RTGETOPT_REQ_STRING },
+ { "--check-not", 'n', RTGETOPT_REQ_STRING },
+ { "--zap", 'z', RTGETOPT_REQ_NOTHING },
+#ifdef MULTI_TARGET_CLIPBOARD
+ { "--target", 't', RTGETOPT_REQ_STRING },
+#endif
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ { "--close", 'k', RTGETOPT_REQ_NOTHING },
+#endif
+ { "--wait", 'w', RTGETOPT_REQ_UINT32 },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }, /* for Usage() */
+};
+
+/** Format descriptors. */
+static CLIPUTILFORMAT g_aFormats[] =
+{
+#if defined(RT_OS_WINDOWS)
+ { "text/ansi", CF_TEXT, NULL, "ANSI text", 0 },
+ { "text/utf-16", CF_UNICODETEXT, NULL, "UTF-16 text", 0 },
+ { "text/utf-8", CF_UNICODETEXT, NULL, "UTF-8 text", CLIPUTILFORMAT_F_CONVERT_UTF8 },
+ /* https://docs.microsoft.com/en-us/windows/desktop/dataxchg/html-clipboard-format */
+ { "text/html", 0, L"HTML Format", "HTML text", 0 },
+ { "bitmap", CF_DIB, NULL, "Bitmap (DIB)", 0 },
+ { "bitmap/v5", CF_DIBV5, NULL, "Bitmap version 5 (DIBv5)", 0 },
+#elif defined(RT_OS_OS2)
+ { "text/ascii", CF_TEXT, NULL, "ASCII text", 0 },
+ { "text/utf-8", CF_TEXT, NULL, "UTF-8 text", CLIPUTILFORMAT_F_CONVERT_UTF8 },
+ { "text/utf-16", 0, "Odin32 UnicodeText", "UTF-16 text", 0},
+#elif defined(RT_OS_DARWIN)
+ { "text/utf-8", kUTTypeUTF8PlainText, "UTF-8 text", 0 },
+ { "text/utf-16", kUTTypeUTF16PlainText, "UTF-16 text", 0 },
+#else
+ /** @todo X11 */
+ { "text/utf-8", None, "UTF8_STRING", "UTF-8 text", 0 },
+#endif
+};
+
+#ifdef MULTI_TARGET_CLIPBOARD
+/** Target descriptors. */
+static CLIPUTILTARGET g_aTargets[] =
+{
+ { "clipboard", 0, "CLIPBOARD", "XA_CLIPBOARD: The clipboard (default)" },
+ { "primary", XA_PRIMARY, NULL, "XA_PRIMARY: Primary selected text (middle mouse button)" },
+ { "secondary", XA_SECONDARY, NULL, "XA_SECONDARY: Secondary selected text (with ctrl)" },
+};
+
+/** The current clipboard target. */
+static CLIPUTILTARGET *g_pTarget = &g_aTargets[0];
+#endif /* MULTI_TARGET_CLIPBOARD */
+
+/** The -v/-q state. */
+static unsigned g_uVerbosity = 1;
+
+#ifdef RT_OS_DARWIN
+
+#elif defined(RT_OS_OS2)
+/** Anchorblock handle. */
+static HAB g_hOs2Ab = NULLHANDLE;
+/** The message queue handle. */
+static HMQ g_hOs2MsgQueue = NULLHANDLE;
+/** Windows that becomes clipboard owner when setting data. */
+static HWND g_hOs2Wnd = NULLHANDLE;
+/** Set if we've opened the clipboard. */
+static bool g_fOs2OpenedClipboard = false;
+/** Set if we're the clipboard owner. */
+static bool g_fOs2ClipboardOwner = false;
+/** Set when we receive a WM_TIMER message during DoWait(). */
+static bool volatile g_fOs2TimerTicked = false;
+
+#elif defined(RT_OS_WINDOWS)
+/** Set if we've opened the clipboard. */
+static bool g_fWinOpenedClipboard = false;
+/** Set when we receive a WM_TIMER message during DoWait(). */
+static bool volatile g_fWinTimerTicked = false;
+/** Window that becomes clipboard owner when setting data. */
+static HWND g_hWinWnd = NULL;
+
+#else
+/** Number of errors (incremented by error handle callback). */
+static uint32_t volatile g_cX11Errors;
+/** The X11 display. */
+static Display *g_pX11Display = NULL;
+/** The X11 dummy window. */
+static Window g_hX11Window = 0;
+/** TARGETS */
+static Atom g_uX11AtomTargets;
+/** MULTIPLE */
+static Atom g_uX11AtomMultiple;
+
+#endif
+
+
+/**
+ * Gets a format descriptor, complaining if invalid format.
+ *
+ * @returns Pointer to the descriptor if found, NULL + msg if not.
+ * @param pszFormat The format to get.
+ */
+static PCCLIPUTILFORMAT GetFormatDesc(const char *pszFormat)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(g_aFormats); i++)
+ if (strcmp(pszFormat, g_aFormats[i].pszName) == 0)
+ {
+#if defined(RT_OS_DARWIN)
+ /** @todo */
+
+#elif defined(RT_OS_OS2)
+ if (g_aFormats[i].pszFormat && g_aFormats[i].fFormat == 0)
+ {
+ g_aFormats[i].fFormat = WinAddAtom(WinQuerySystemAtomTable(), g_aFormats[i].pszFormat);
+ if (g_aFormats[i].fFormat == 0)
+ RTMsgError("WinAddAtom(,%s) failed: %#x", g_aFormats[i].pszFormat, WinGetLastError(g_hOs2Ab));
+ }
+
+#elif defined(RT_OS_WINDOWS)
+ if (g_aFormats[i].pwszFormat && g_aFormats[i].fFormat == 0)
+ {
+ g_aFormats[i].fFormat = RegisterClipboardFormatW(g_aFormats[i].pwszFormat);
+ if (g_aFormats[i].fFormat == 0)
+ RTMsgError("RegisterClipboardFormatW(%ls) failed: %u (%#x)",
+ g_aFormats[i].pwszFormat, GetLastError(), GetLastError());
+ }
+#elif defined(CU_X11)
+ if (g_aFormats[i].pszAtomName && g_aFormats[i].uAtom == 0)
+ g_aFormats[i].uAtom = XInternAtom(g_pX11Display, g_aFormats[i].pszAtomName, False);
+#endif
+ return &g_aFormats[i];
+ }
+
+ /*
+ * Try register the format.
+ */
+ static CLIPUTILFORMAT AdHoc;
+ AdHoc.pszName = pszFormat;
+ AdHoc.pszDesc = pszFormat;
+ AdHoc.fFlags = CLIPUTILFORMAT_F_AD_HOC;
+#ifdef RT_OS_DARWIN
+/** @todo */
+
+#elif defined(RT_OS_OS2)
+ AdHoc.pszFormat = pszFormat;
+ AdHoc.fFormat = WinAddAtom(WinQuerySystemAtomTable(), pszFormat);
+ if (AdHoc.fFormat == 0)
+ {
+ RTMsgError("Invalid format '%s' (%#x)", pszFormat, WinGetLastError(g_hOs2Ab));
+ return NULL;
+ }
+
+#elif defined(RT_OS_WINDOWS)
+ AdHoc.pwszFormat = NULL;
+ AdHoc.fFormat = RegisterClipboardFormatA(pszFormat);
+ if (AdHoc.fFormat == 0)
+ {
+ RTMsgError("RegisterClipboardFormatA(%s) failed: %u (%#x)", pszFormat, GetLastError(), GetLastError());
+ return NULL;
+ }
+
+#else
+ AdHoc.pszAtomName = pszFormat;
+ AdHoc.uAtom = XInternAtom(g_pX11Display, pszFormat, False);
+ if (AdHoc.uAtom == None)
+ {
+ RTMsgError("Invalid format '%s' or out of memory for X11 atoms", pszFormat);
+ return NULL;
+ }
+
+#endif
+ return &AdHoc;
+}
+
+
+#ifdef RT_OS_DARWIN
+
+/** @todo */
+
+
+#elif defined(RT_OS_OS2)
+
+/**
+ * The window procedure for the object window.
+ *
+ * @returns Message result.
+ *
+ * @param hwnd The window handle.
+ * @param msg The message.
+ * @param mp1 Message parameter 1.
+ * @param mp2 Message parameter 2.
+ */
+static MRESULT EXPENTRY CuOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+ if (g_uVerbosity > 2)
+ RTMsgInfo("CuOs2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2);
+
+ switch (msg)
+ {
+ case WM_CREATE:
+ return NULL; /* FALSE(/NULL) == Continue*/
+ case WM_DESTROY:
+ break;
+
+ /*
+ * Clipboard viewer message - the content has been changed.
+ * This is sent *after* releasing the clipboard sem
+ * and during the WinSetClipbrdViewer call.
+ */
+ case WM_DRAWCLIPBOARD:
+ break;
+
+ /*
+ * Clipboard owner message - the content was replaced.
+ * This is sent by someone with an open clipboard, so don't try open it now.
+ */
+ case WM_DESTROYCLIPBOARD:
+ break;
+
+ /*
+ * Clipboard owner message - somebody is requesting us to render a format.
+ * This is called by someone which owns the clipboard, but that's fine.
+ */
+ case WM_RENDERFMT:
+ break;
+
+ /*
+ * Clipboard owner message - we're about to quit and should render all formats.
+ */
+ case WM_RENDERALLFMTS:
+ break;
+
+ /*
+ * Clipboard owner messages dealing with owner drawn content.
+ * We shouldn't be seeing any of these.
+ */
+ case WM_PAINTCLIPBOARD:
+ case WM_SIZECLIPBOARD:
+ case WM_HSCROLLCLIPBOARD:
+ case WM_VSCROLLCLIPBOARD:
+ AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
+ break;
+
+ /*
+ * We shouldn't be seeing any other messages according to the docs.
+ * But for whatever reason, PM sends us a WM_ADJUSTWINDOWPOS message
+ * during WinCreateWindow. So, ignore that and assert on anything else.
+ */
+ default:
+ AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
+ case WM_ADJUSTWINDOWPOS:
+ break;
+
+ /*
+ * We use this window fielding WM_TIMER during DoWait.
+ */
+ case WM_TIMER:
+ if (SHORT1FROMMP(mp1) == 1)
+ g_fOs2TimerTicked = true;
+ break;
+ }
+ return NULL;
+}
+
+
+/**
+ * Initialize the OS/2 bits.
+ */
+static RTEXITCODE CuOs2Init(void)
+{
+ g_hOs2Ab = WinInitialize(0);
+ if (g_hOs2Ab == NULLHANDLE)
+ return RTMsgErrorExitFailure("WinInitialize failed!");
+
+ g_hOs2MsgQueue = WinCreateMsgQueue(g_hOs2Ab, 10);
+ if (g_hOs2MsgQueue == NULLHANDLE)
+ return RTMsgErrorExitFailure("WinCreateMsgQueue failed: %#x", WinGetLastError(g_hOs2Ab));
+
+ static char s_szClass[] = "VBox-ClipUtilClipboardClass";
+ if (!WinRegisterClass(g_hOs2Wnd, (PCSZ)s_szClass, CuOs2WinProc, 0, 0))
+ return RTMsgErrorExitFailure("WinRegisterClass failed: %#x", WinGetLastError(g_hOs2Ab));
+
+ g_hOs2Wnd = WinCreateWindow(HWND_OBJECT, /* hwndParent */
+ (PCSZ)s_szClass, /* pszClass */
+ (PCSZ)"VirtualBox Clipboard Utility", /* pszName */
+ 0, /* flStyle */
+ 0, 0, 0, 0, /* x, y, cx, cy */
+ NULLHANDLE, /* hwndOwner */
+ HWND_BOTTOM, /* hwndInsertBehind */
+ 42, /* id */
+ NULL, /* pCtlData */
+ NULL); /* pPresParams */
+ if (g_hOs2Wnd == NULLHANDLE)
+ return RTMsgErrorExitFailure("WinCreateWindow failed: %#x", WinGetLastError(g_hOs2Ab));
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Terminates the OS/2 bits.
+ */
+static RTEXITCODE CuOs2Term(void)
+{
+ if (g_fOs2OpenedClipboard)
+ {
+ if (!WinCloseClipbrd(g_hOs2Ab))
+ return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
+ g_fOs2OpenedClipboard = false;
+ }
+
+ WinDestroyWindow(g_hOs2Wnd);
+ g_hOs2Wnd = NULLHANDLE;
+
+ WinDestroyMsgQueue(g_hOs2MsgQueue);
+ g_hOs2MsgQueue = NULLHANDLE;
+
+ WinTerminate(g_hOs2Ab);
+ g_hOs2Ab = NULLHANDLE;
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Opens the OS/2 clipboard.
+ */
+static RTEXITCODE CuOs2OpenClipboardIfNecessary(void)
+{
+ if (g_fOs2OpenedClipboard)
+ return RTEXITCODE_SUCCESS;
+ if (WinOpenClipbrd(g_hOs2Ab))
+ {
+ if (g_uVerbosity > 0)
+ RTMsgInfo("Opened the clipboard\n");
+ g_fOs2OpenedClipboard = true;
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("WinOpenClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
+}
+
+
+#elif defined(RT_OS_WINDOWS)
+
+/**
+ * Window procedure for the clipboard owner window on Windows.
+ */
+static LRESULT CALLBACK CuWinWndProc(HWND hWnd, UINT idMsg, WPARAM wParam, LPARAM lParam) RT_NOTHROW_DEF
+{
+ if (g_uVerbosity > 2)
+ RTMsgInfo("CuWinWndProc: hWnd=%p idMsg=%#05x wParam=%#zx lParam=%#zx\n", hWnd, idMsg, wParam, lParam);
+
+ switch (idMsg)
+ {
+ case WM_TIMER:
+ if (wParam == 1)
+ g_fWinTimerTicked = true;
+ break;
+ }
+ return DefWindowProc(hWnd, idMsg, wParam, lParam);
+}
+
+
+/**
+ * Initialize the Windows bits.
+ */
+static RTEXITCODE CuWinInit(void)
+{
+ /* Register the window class: */
+ static wchar_t s_wszClass[] = L"VBox-ClipUtilClipboardClass";
+ WNDCLASSW WndCls = {0};
+ WndCls.style = CS_NOCLOSE;
+ WndCls.lpfnWndProc = CuWinWndProc;
+ WndCls.cbClsExtra = 0;
+ WndCls.cbWndExtra = 0;
+ WndCls.hInstance = (HINSTANCE)GetModuleHandle(NULL);
+ WndCls.hIcon = NULL;
+ WndCls.hCursor = NULL;
+ WndCls.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
+ WndCls.lpszMenuName = NULL;
+ WndCls.lpszClassName = s_wszClass;
+
+ ATOM uAtomWndClass = RegisterClassW(&WndCls);
+ if (!uAtomWndClass)
+ return RTMsgErrorExitFailure("RegisterClassW failed: %u (%#x)", GetLastError(), GetLastError());
+
+ /* Create the clipboard owner window: */
+ g_hWinWnd = CreateWindowExW(WS_EX_TRANSPARENT, /* fExStyle */
+ s_wszClass, /* pwszClass */
+ L"VirtualBox Clipboard Utility", /* pwszName */
+ 0, /* fStyle */
+ 0, 0, 0, 0, /* x, y, cx, cy */
+ HWND_MESSAGE, /* hWndParent */
+ NULL, /* hMenu */
+ (HINSTANCE)GetModuleHandle(NULL), /* hinstance */
+ NULL); /* pParam */
+ if (g_hWinWnd == NULL)
+ return RTMsgErrorExitFailure("CreateWindowExW failed: %u (%#x)", GetLastError(), GetLastError());
+
+ return RTEXITCODE_SUCCESS;
+}
+
+/**
+ * Terminates the Windows bits.
+ */
+static RTEXITCODE CuWinTerm(void)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ if (g_fWinOpenedClipboard)
+ {
+ if (CloseClipboard())
+ g_fWinOpenedClipboard = false;
+ else
+ rcExit = RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError());
+ }
+
+ if (g_hWinWnd != NULL)
+ {
+ if (!DestroyWindow(g_hWinWnd))
+ rcExit = RTMsgErrorExitFailure("DestroyWindow failed: %u (%#x)", GetLastError(), GetLastError());
+ g_hWinWnd = NULL;
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Opens the window clipboard.
+ */
+static RTEXITCODE WinOpenClipboardIfNecessary(void)
+{
+ if (g_fWinOpenedClipboard)
+ return RTEXITCODE_SUCCESS;
+ if (OpenClipboard(g_hWinWnd))
+ {
+ if (g_uVerbosity > 0)
+ RTMsgInfo("Opened the clipboard\n");
+ g_fWinOpenedClipboard = true;
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("OpenClipboard failed: %u (%#x)", GetLastError(), GetLastError());
+}
+
+
+#else /* X11: */
+
+/**
+ * Error handler callback.
+ */
+static int CuX11ErrorCallback(Display *pX11Display, XErrorEvent *pErrEvt)
+{
+ g_cX11Errors++;
+ char szErr[2048];
+ XGetErrorText(pX11Display, pErrEvt->error_code, szErr, sizeof(szErr));
+ RTMsgError("An X Window protocol error occurred: %s\n"
+ " Request code: %u\n"
+ " Minor code: %u\n"
+ " Serial number of the failed request: %u\n",
+ szErr, pErrEvt->request_code, pErrEvt->minor_code, pErrEvt->serial);
+ return 0;
+}
+
+
+/**
+ * Initialize the X11 bits.
+ */
+static RTEXITCODE CuX11Init(void)
+{
+ /*
+ * Open the X11 display and create a little dummy window.
+ */
+ XSetErrorHandler(CuX11ErrorCallback);
+ g_pX11Display = XOpenDisplay(NULL);
+ if (!g_pX11Display)
+ return RTMsgErrorExitFailure("XOpenDisplay failed");
+
+ int const iDefaultScreen = DefaultScreen(g_pX11Display);
+ g_hX11Window = XCreateSimpleWindow(g_pX11Display,
+ RootWindow(g_pX11Display, iDefaultScreen),
+ 0 /*x*/, 0 /*y*/,
+ 1 /*cx*/, 1 /*cy*/,
+ 0 /*cPxlBorder*/,
+ BlackPixel(g_pX11Display, iDefaultScreen) /*Border*/,
+ WhitePixel(g_pX11Display, iDefaultScreen) /*Background*/);
+
+ /*
+ * Resolve any unknown atom values we might need later.
+ */
+ for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++)
+ if (g_aTargets[i].pszAtomName)
+ {
+ g_aTargets[i].uAtom = XInternAtom(g_pX11Display, g_aTargets[i].pszAtomName, False);
+ if (g_uVerbosity > 2)
+ RTPrintf("target %s atom=%#x\n", g_aTargets[i].pszName, g_aTargets[i].uAtom);
+ }
+
+ g_uX11AtomTargets = XInternAtom(g_pX11Display, "TARGETS", False);
+ g_uX11AtomMultiple = XInternAtom(g_pX11Display, "MULTIPLE", False);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+#endif /* X11 */
+
+
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+/**
+ * Closes the clipboard if open.
+ */
+static RTEXITCODE CuCloseClipboard(void)
+{
+# if defined(RT_OS_OS2)
+ if (g_fOs2OpenedClipboard)
+ {
+ if (!WinCloseClipbrd(g_hOs2Ab))
+ return RTMsgErrorExitFailure("WinCloseClipbrd failed: %#x", WinGetLastError(g_hOs2Ab));
+ g_fOs2OpenedClipboard = false;
+ if (g_uVerbosity > 0)
+ RTMsgInfo("Closed the clipboard.\n");
+ }
+# else
+ if (g_fWinOpenedClipboard)
+ {
+ if (!CloseClipboard())
+ return RTMsgErrorExitFailure("CloseClipboard failed: %u (%#x)", GetLastError(), GetLastError());
+ g_fWinOpenedClipboard = false;
+ if (g_uVerbosity > 0)
+ RTMsgInfo("Closed the clipboard.\n");
+ }
+# endif
+ else if (g_uVerbosity > 0)
+ RTMsgInfo("No need to close clipboard, not opened.\n");
+
+ return RTEXITCODE_SUCCESS;
+}
+#endif /* RT_OS_OS2 || RT_OS_WINDOWS */
+
+
+/**
+ * Lists the clipboard format.
+ */
+static RTEXITCODE ListClipboardContent(void)
+{
+#if defined(RT_OS_OS2)
+ RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ HATOMTBL const hAtomTbl = WinQuerySystemAtomTable();
+ uint32_t idx = 0;
+ ULONG fFormat = 0;
+ while ((fFormat = WinEnumClipbrdFmts(g_hOs2Ab)) != 0)
+ {
+ char szName[256] = {0};
+ ULONG cchRet = WinQueryAtomName(hAtomTbl, fFormat, szName, sizeof(szName));
+ if (cchRet != 0)
+ RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, szName);
+ else
+ {
+ const char *pszName = NULL;
+ switch (fFormat)
+ {
+ case CF_TEXT: pszName = "CF_TEXT"; break;
+ case CF_BITMAP: pszName = "CF_BITMAP"; break;
+ case CF_DSPTEXT: pszName = "CF_DSPTEXT"; break;
+ case CF_DSPBITMAP: pszName = "CF_DSPBITMAP"; break;
+ case CF_METAFILE: pszName = "CF_METAFILE"; break;
+ case CF_DSPMETAFILE: pszName = "CF_DSPMETAFILE"; break;
+ case CF_PALETTE: pszName = "CF_PALETTE"; break;
+ default:
+ break;
+ }
+ if (pszName)
+ RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, pszName);
+ else
+ RTPrintf("#%02u: %#06x\n", idx, fFormat);
+ }
+
+ idx++;
+ }
+ }
+
+ return rcExit;
+
+#elif defined(RT_OS_WINDOWS)
+ RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ SetLastError(0);
+ uint32_t idx = 0;
+ UINT fFormat = 0;
+ while ((fFormat = EnumClipboardFormats(fFormat)) != 0)
+ {
+ WCHAR wszName[256];
+ int cchName = GetClipboardFormatNameW(fFormat, wszName, RT_ELEMENTS(wszName));
+ if (cchName > 0)
+ RTPrintf("#%02u: %#06x - %ls\n", idx, fFormat, wszName);
+ else
+ {
+ const char *pszName = NULL;
+ switch (fFormat)
+ {
+ case CF_TEXT: pszName = "CF_TEXT"; break;
+ case CF_BITMAP: pszName = "CF_BITMAP"; break;
+ case CF_METAFILEPICT: pszName = "CF_METAFILEPICT"; break;
+ case CF_SYLK: pszName = "CF_SYLK"; break;
+ case CF_DIF: pszName = "CF_DIF"; break;
+ case CF_TIFF: pszName = "CF_TIFF"; break;
+ case CF_OEMTEXT: pszName = "CF_OEMTEXT"; break;
+ case CF_DIB: pszName = "CF_DIB"; break;
+ case CF_PALETTE: pszName = "CF_PALETTE"; break;
+ case CF_PENDATA: pszName = "CF_PENDATA"; break;
+ case CF_RIFF: pszName = "CF_RIFF"; break;
+ case CF_WAVE: pszName = "CF_WAVE"; break;
+ case CF_UNICODETEXT: pszName = "CF_UNICODETEXT"; break;
+ case CF_ENHMETAFILE: pszName = "CF_ENHMETAFILE"; break;
+ case CF_HDROP: pszName = "CF_HDROP"; break;
+ case CF_LOCALE: pszName = "CF_LOCALE"; break;
+ case CF_DIBV5: pszName = "CF_DIBV5"; break;
+ default:
+ break;
+ }
+ if (pszName)
+ RTPrintf("#%02u: %#06x - %s\n", idx, fFormat, pszName);
+ else
+ RTPrintf("#%02u: %#06x\n", idx, fFormat);
+ }
+
+ idx++;
+ }
+ if (idx == 0)
+ RTPrintf("Empty\n");
+ }
+ return rcExit;
+
+#elif defined(CU_X11)
+ /* Request the TARGETS property: */
+ Atom uAtomDst = g_uX11AtomTargets;
+ int rc = XConvertSelection(g_pX11Display, g_pTarget->uAtom, g_uX11AtomTargets, uAtomDst, g_hX11Window, CurrentTime);
+ if (g_uVerbosity > 1)
+ RTPrintf("XConvertSelection -> %d\n", rc);
+
+ /* Wait for the reply: */
+ for (;;)
+ {
+ XEvent Evt = {0};
+ rc = XNextEvent(g_pX11Display, &Evt);
+ if (Evt.type == SelectionNotify)
+ {
+ if (g_uVerbosity > 1)
+ RTPrintf("XNextEvent -> %d; type=SelectionNotify\n", rc);
+ if (Evt.xselection.selection == g_pTarget->uAtom)
+ {
+ if (Evt.xselection.property == None)
+ return RTMsgErrorExitFailure("XConvertSelection(,%s,TARGETS,) failed", g_pTarget->pszName);
+
+ /* Get the TARGETS property data: */
+ Atom uAtomRetType = 0;
+ int iActualFmt = 0;
+ unsigned long cbLeftToRead = 0;
+ unsigned long cItems = 0;
+ unsigned char *pbData = NULL;
+ rc = XGetWindowProperty(g_pX11Display, g_hX11Window, uAtomDst,
+ 0 /*offset*/, sizeof(Atom) * 4096 /* should be enough */, True /*fDelete*/, XA_ATOM,
+ &uAtomRetType, &iActualFmt, &cItems, &cbLeftToRead, &pbData);
+ if (g_uVerbosity > 1)
+ RTPrintf("XConvertSelection -> %d; uAtomRetType=%u iActualFmt=%d cItems=%lu cbLeftToRead=%lu pbData=%p\n",
+ rc, uAtomRetType, iActualFmt, cItems, cbLeftToRead, pbData);
+ if (pbData && cItems > 0)
+ {
+ /* Display the TARGETS: */
+ Atom const *paTargets = (Atom const *)pbData;
+ for (unsigned long i = 0; i < cItems; i++)
+ {
+ const char *pszName = XGetAtomName(g_pX11Display, paTargets[i]);
+ if (pszName)
+ RTPrintf("#%02u: %#06x - %s\n", i, paTargets[i], pszName);
+ else
+ RTPrintf("#%02u: %#06x\n", i, paTargets[i]);
+ }
+ }
+ else
+ RTMsgInfo("Empty");
+ if (pbData)
+ XFree(pbData);
+ return RTEXITCODE_SUCCESS;
+ }
+ }
+ else if (g_uVerbosity > 1)
+ RTPrintf("XNextEvent -> %d; type=%d\n", rc, Evt.type);
+ }
+
+#else
+ return RTMsgErrorExitFailure("ListClipboardContent is not implemented");
+#endif
+}
+
+
+/**
+ * Reads the given clipboard format and stores it in on the heap.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to get.
+ * @param ppvData Where to return the pointer to the data. Free using
+ * RTMemFree when done.
+ * @param pcbData Where to return the amount of data returned.
+ */
+static RTEXITCODE ReadClipboardData(PCCLIPUTILFORMAT pFmtDesc, void **ppvData, size_t *pcbData)
+{
+ *ppvData = NULL;
+ *pcbData = 0;
+
+#if defined(RT_OS_OS2)
+ RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ ULONG fFmtInfo = 0;
+ if (WinQueryClipbrdFmtInfo(g_hOs2Ab, pFmtDesc->fFormat, &fFmtInfo))
+ {
+ ULONG uData = WinQueryClipbrdData(g_hOs2Ab, pFmtDesc->fFormat);
+ if (fFmtInfo & CFI_POINTER)
+ {
+ PCLIPHEADER pOdinHdr = (PCLIPHEADER)uData;
+ if (pFmtDesc->fFormat == CF_TEXT)
+ {
+ if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8)
+ {
+ char *pszUtf8 = NULL;
+ int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)uData);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbData = strlen(pszUtf8) + 1;
+ *ppvData = RTMemDup(pszUtf8, *pcbData);
+ RTStrFree(pszUtf8);
+ }
+ else
+ return RTMsgErrorExitFailure("RTStrCurrentCPToUtf8 failed: %Rrc", rc);
+ }
+ else
+ {
+ *pcbData = strlen((const char *)uData) + 1;
+ *ppvData = RTMemDup((const char *)uData, *pcbData);
+ }
+ }
+ else if ( strcmp(pFmtDesc->pszFormat, "Odin32 UnicodeText") == 0
+ && memcmp(pOdinHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pOdinHdr->achMagic)) == 0)
+ {
+ *pcbData = pOdinHdr->cbData;
+ *ppvData = RTMemDup(pOdinHdr + 1, pOdinHdr->cbData);
+ }
+ else
+ {
+ /* We could use DosQueryMem here to figure out the size of the allocation... */
+ *pcbData = PAGE_SIZE - (uData & PAGE_OFFSET_MASK);
+ *ppvData = RTMemDup((void const *)uData, *pcbData);
+ }
+ }
+ else
+ {
+ *pcbData = sizeof(uData);
+ *ppvData = RTMemDup(&uData, sizeof(uData));
+ }
+ if (!*ppvData)
+ rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", *pcbData);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("WinQueryClipbrdFmtInfo(,%s,) failed: %#x\n",
+ pFmtDesc->pszName, WinGetLastError(g_hOs2Ab));
+ }
+ return rcExit;
+
+#elif defined(RT_OS_WINDOWS)
+ RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ HANDLE hData = GetClipboardData(pFmtDesc->fFormat);
+ if (hData != NULL)
+ {
+ SIZE_T const cbData = GlobalSize(hData);
+ PVOID const pvData = GlobalLock(hData);
+ if (pvData != NULL)
+ {
+ *pcbData = cbData;
+ if (cbData != 0)
+ {
+ if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8)
+ {
+ char *pszUtf8 = NULL;
+ size_t cchUtf8 = 0;
+ int rc = RTUtf16ToUtf8Ex((PCRTUTF16)pvData, cbData / sizeof(RTUTF16), &pszUtf8, 0, &cchUtf8);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbData = cchUtf8 + 1;
+ *ppvData = RTMemDup(pszUtf8, cchUtf8 + 1);
+ RTStrFree(pszUtf8);
+ if (!*ppvData)
+ rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", cbData);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTUtf16ToUtf8Ex failed: %Rrc", rc);
+ }
+ else
+ {
+ *ppvData = RTMemDup(pvData, cbData);
+ if (!*ppvData)
+ rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", cbData);
+ }
+ }
+ GlobalUnlock(hData);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("GetClipboardData(%s) failed: %u (%#x)\n",
+ pFmtDesc->pszName, GetLastError(), GetLastError());
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("GetClipboardData(%s) failed: %u (%#x)\n",
+ pFmtDesc->pszName, GetLastError(), GetLastError());
+ }
+ return rcExit;
+
+#elif defined(CU_X11)
+
+ /* Request the data: */
+ Atom const uAtomDst = pFmtDesc->uAtom;
+ int rc = XConvertSelection(g_pX11Display, g_pTarget->uAtom, pFmtDesc->uAtom, uAtomDst, g_hX11Window, CurrentTime);
+ if (g_uVerbosity > 1)
+ RTPrintf("XConvertSelection -> %d\n", rc);
+
+ /* Wait for the reply: */
+ for (;;)
+ {
+ XEvent Evt = {0};
+ rc = XNextEvent(g_pX11Display, &Evt);
+ if (Evt.type == SelectionNotify)
+ {
+ if (g_uVerbosity > 1)
+ RTPrintf("XNextEvent -> %d; type=SelectionNotify\n", rc);
+ if (Evt.xselection.selection == g_pTarget->uAtom)
+ {
+ if (Evt.xselection.property == None)
+ return RTMsgErrorExitFailure("XConvertSelection(,%s,%s,) failed", g_pTarget->pszName, pFmtDesc->pszName);
+
+ /*
+ * Retrieve the data.
+ */
+ Atom uAtomRetType = 0;
+ int cBitsActualFmt = 0;
+ unsigned long cbLeftToRead = 0;
+ unsigned long cItems = 0;
+ unsigned char *pbData = NULL;
+ rc = XGetWindowProperty(g_pX11Display, g_hX11Window, uAtomDst,
+ 0 /*offset*/, _64M, False/*fDelete*/, AnyPropertyType,
+ &uAtomRetType, &cBitsActualFmt, &cItems, &cbLeftToRead, &pbData);
+ if (g_uVerbosity > 1)
+ RTPrintf("XConvertSelection -> %d; uAtomRetType=%u cBitsActualFmt=%d cItems=%lu cbLeftToRead=%lu pbData=%p\n",
+ rc, uAtomRetType, cBitsActualFmt, cItems, cbLeftToRead, pbData);
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ if (pbData && cItems > 0)
+ {
+ *pcbData = cItems * (cBitsActualFmt / 8);
+ *ppvData = RTMemDup(pbData, *pcbData);
+ if (!*ppvData)
+ rcExit = RTMsgErrorExitFailure("Out of memory allocating %#zx bytes.", *pcbData);
+ }
+ if (pbData)
+ XFree(pbData);
+ XDeleteProperty(g_pX11Display, g_hX11Window, uAtomDst);
+ return rcExit;
+ }
+ }
+ else if (g_uVerbosity > 1)
+ RTPrintf("XNextEvent -> %d; type=%d\n", rc, Evt.type);
+ }
+
+#else
+ RT_NOREF(pFmtDesc);
+ return RTMsgErrorExitFailure("ReadClipboardData is not implemented\n");
+#endif
+}
+
+
+/**
+ * Puts the given data and format on the clipboard.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format.
+ * @param pvData The data.
+ * @param cbData The amount of data in bytes.
+ */
+static RTEXITCODE WriteClipboardData(PCCLIPUTILFORMAT pFmtDesc, void const *pvData, size_t cbData)
+{
+#if defined(RT_OS_OS2)
+ RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /** @todo do we need to become owner? */
+
+ /* Convert to local code page if needed: */
+ char *pszLocale = NULL;
+ if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8)
+ {
+ int rc = RTStrUtf8ToCurrentCPEx(&pszLocale, (char *)pvData, cbData);
+ if (RT_SUCCESS(rc))
+ {
+ pvData = pszLocale;
+ cbData = strlen(pszLocale) + 1;
+ }
+ else
+ return RTMsgErrorExitFailure("RTStrUtf8ToCurrentCPEx failed: %Rrc\n", rc);
+ }
+
+ /* Allocate a bunch of shared memory for the object. */
+ PVOID pvShared = NULL;
+ APIRET orc = DosAllocSharedMem(&pvShared, NULL, cbData,
+ OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
+ if (orc == NO_ERROR)
+ {
+ memcpy(pvShared, pvData, cbData);
+
+ if (WinSetClipbrdData(g_hOs2Ab, (uintptr_t)pvShared, pFmtDesc->fFormat, CFI_POINTER))
+ {
+ if (g_uVerbosity > 0)
+ RTMsgInfo("Put '%s' on the clipboard: %p LB %zu\n", pFmtDesc->pszName, pvShared, cbData);
+ rcExit = RTEXITCODE_SUCCESS;
+ }
+ else
+ {
+ rcExit = RTMsgErrorExitFailure("WinSetClipbrdData(,%p LB %#x,%s,) failed: %#x\n",
+ pvShared, cbData, pFmtDesc->pszName, WinGetLastError(g_hOs2Ab));
+ DosFreeMem(pvShared);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("DosAllocSharedMem(,, %#x,) -> %u", cbData, orc);
+ RTStrFree(pszLocale);
+ }
+ return rcExit;
+
+
+#elif defined(RT_OS_WINDOWS)
+ RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Do input data conversion.
+ */
+ PRTUTF16 pwszFree = NULL;
+ if (pFmtDesc->fFlags & CLIPUTILFORMAT_F_CONVERT_UTF8)
+ {
+ size_t cwcConv = 0;
+ int rc = RTStrToUtf16Ex((char const *)pvData, cbData, &pwszFree, 0, &cwcConv);
+ if (RT_SUCCESS(rc))
+ {
+ pvData = pwszFree;
+ cbData = cwcConv * sizeof(RTUTF16);
+ }
+ else
+ return RTMsgErrorExitFailure("RTStrToTUtf16Ex failed: %Rrc\n", rc);
+ }
+
+ /*
+ * Text formats generally include the zero terminator.
+ */
+ uint32_t cbZeroPadding = 0;
+ if (pFmtDesc->fFormat == CF_UNICODETEXT)
+ cbZeroPadding = sizeof(WCHAR);
+ else if (pFmtDesc->fFormat == CF_TEXT)
+ cbZeroPadding = sizeof(char);
+
+ HANDLE hDstData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, cbData + cbZeroPadding);
+ if (hDstData)
+ {
+ if (cbData)
+ {
+ PVOID pvDstData = GlobalLock(hDstData);
+ if (pvDstData)
+ memcpy(pvDstData, pvData, cbData);
+ else
+ rcExit = RTMsgErrorExitFailure("GlobalLock failed: %u (%#x)\n", GetLastError(), GetLastError());
+ }
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (SetClipboardData(pFmtDesc->fFormat, hDstData))
+ {
+ if (g_uVerbosity > 0)
+ RTMsgInfo("Put '%s' on the clipboard: %p LB %zu\n", pFmtDesc->pszName, hDstData, cbData + cbZeroPadding);
+ }
+ else
+ {
+ rcExit = RTMsgErrorExitFailure("SetClipboardData(%s) failed: %u (%#x)\n",
+ pFmtDesc->pszName, GetLastError(), GetLastError());
+ GlobalFree(hDstData);
+ }
+ }
+ else
+ GlobalFree(hDstData);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("GlobalAlloc(,%#zx) failed: %u (%#x)\n",
+ cbData + cbZeroPadding, GetLastError(), GetLastError());
+ }
+ return rcExit;
+
+#else
+ RT_NOREF(pFmtDesc, pvData, cbData);
+ return RTMsgErrorExitFailure("WriteClipboardData is not implemented\n");
+#endif
+}
+
+
+/**
+ * Check if the given data + format matches what's actually on the clipboard.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to compare.
+ * @param pvExpect The expected clipboard data.
+ * @param cbExpect The size of the expected clipboard data.
+ */
+static RTEXITCODE CompareDataWithClipboard(PCCLIPUTILFORMAT pFmtDesc, void const *pvExpect, size_t cbExpect)
+{
+ void *pvData = NULL;
+ size_t cbData = 0;
+ RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if ( cbData == cbExpect
+ && memcmp(pvData, pvExpect, cbData) == 0)
+ rcExit = RTEXITCODE_SUCCESS;
+ else
+ rcExit = RTMsgErrorExitFailure("Mismatch for '%s' (cbData=%#zx cbExpect=%#zx)\n",
+ pFmtDesc->pszName, cbData, cbExpect);
+ RTMemFree(pvData);
+ }
+ return rcExit;
+}
+
+
+/**
+ * Gets the given clipboard format.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to get.
+ * @param pStrmOut Where to output the data.
+ * @param fIsStdOut Set if @a pStrmOut is standard output, clear if not.
+ */
+static RTEXITCODE ClipboardContentToStdOut(PCCLIPUTILFORMAT pFmtDesc)
+{
+ void *pvData = NULL;
+ size_t cbData = 0;
+ RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ int rc = RTStrmWrite(g_pStdOut, pvData, cbData);
+ RTMemFree(pvData);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("Error writing %#zx bytes to standard output: %Rrc", cbData, rc);
+ }
+ return rcExit;
+}
+
+
+/**
+ * Gets the given clipboard format.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to get.
+ * @param pszFilename The output filename.
+ */
+static RTEXITCODE ClipboardContentToFile(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename)
+{
+ void *pvData = NULL;
+ size_t cbData = 0;
+ RTEXITCODE rcExit = ReadClipboardData(pFmtDesc, &pvData, &cbData);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszFilename,
+ RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE
+ | (0770 << RTFILE_O_CREATE_MODE_SHIFT));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFile, pvData, cbData, NULL);
+ int const rc2 = RTFileClose(hFile);
+ if (RT_FAILURE(rc) || RT_FAILURE(rc2))
+ {
+ if (RT_FAILURE_NP(rc))
+ RTMsgError("Writing %#z bytes to '%s' failed: %Rrc", cbData, pszFilename, rc);
+ else
+ RTMsgError("Closing '%s' failed: %Rrc", pszFilename, rc2);
+ RTMsgInfo("Deleting '%s'.", pszFilename);
+ RTFileDelete(pszFilename);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Failed to open '%s' for writing: %Rrc", pszFilename, rc);
+ RTMemFree(pvData);
+ }
+ return rcExit;
+}
+
+
+/**
+ * Puts the given format + data onto the clipboard.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to put.
+ * @param pszData The string data.
+ */
+static RTEXITCODE PutStringOnClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszData)
+{
+ return WriteClipboardData(pFmtDesc, pszData, strlen(pszData));
+}
+
+
+/**
+ * Puts a format + file content onto the clipboard.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to put.
+ * @param pszFilename The filename.
+ */
+static RTEXITCODE PutFileOnClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ void *pvData = NULL;
+ size_t cbData = 0;
+ int rc = RTFileReadAll(pszFilename, &pvData, &cbData);
+ if (RT_SUCCESS(rc))
+ {
+ rcExit = WriteClipboardData(pFmtDesc, pvData, cbData);
+ RTFileReadAllFree(pvData, cbData);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Failed to open and read '%s' into memory: %Rrc", pszFilename, rc);
+ return rcExit;
+}
+
+
+/**
+ * Checks if the given format + data matches what's on the clipboard.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to check.
+ * @param pszData The string data.
+ */
+static RTEXITCODE CheckStringAgainstClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszData)
+{
+ return CompareDataWithClipboard(pFmtDesc, pszData, strlen(pszData));
+}
+
+
+/**
+ * Check if the given format + file content matches what's on the clipboard.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to check.
+ * @param pszFilename The filename.
+ */
+static RTEXITCODE CheckFileAgainstClipboard(PCCLIPUTILFORMAT pFmtDesc, const char *pszFilename)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ void *pvData = NULL;
+ size_t cbData = 0;
+ int rc = RTFileReadAll(pszFilename, &pvData, &cbData);
+ if (RT_SUCCESS(rc))
+ {
+ rcExit = CompareDataWithClipboard(pFmtDesc, pvData, cbData);
+ RTFileReadAllFree(pvData, cbData);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Failed to open and read '%s' into memory: %Rrc", pszFilename, rc);
+ return rcExit;
+}
+
+
+/**
+ * Check that the given format is not on the clipboard.
+ *
+ * @returns Success indicator.
+ * @param pFmtDesc The format to check.
+ */
+static RTEXITCODE CheckFormatNotOnClipboard(PCCLIPUTILFORMAT pFmtDesc)
+{
+#if defined(RT_OS_OS2)
+ RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ ULONG fFmtInfo = 0;
+ if (WinQueryClipbrdFmtInfo(g_hOs2Ab, pFmtDesc->fFormat, &fFmtInfo))
+ rcExit = RTMsgErrorExitFailure("Format '%s' is present");
+ }
+ return rcExit;
+
+#elif defined(RT_OS_WINDOWS)
+ RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (IsClipboardFormatAvailable(pFmtDesc->fFormat))
+ rcExit = RTMsgErrorExitFailure("Format '%s' is present");
+ }
+ return rcExit;
+
+#else
+ RT_NOREF(pFmtDesc);
+ return RTMsgErrorExitFailure("CheckFormatNotOnClipboard is not implemented");
+#endif
+}
+
+
+/**
+ * Empties the clipboard.
+ *
+ * @returns Success indicator.
+ */
+static RTEXITCODE ZapAllClipboardData(void)
+{
+#if defined(RT_OS_OS2)
+ RTEXITCODE rcExit = CuOs2OpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ ULONG fFmtInfo = 0;
+ if (WinEmptyClipbrd(g_hOs2Ab))
+ {
+ WinSetClipbrdOwner(g_hOs2Ab, g_hOs2Wnd); /* Probably unnecessary? */
+ WinSetClipbrdOwner(g_hOs2Ab, NULLHANDLE);
+ g_fOs2ClipboardOwner = false;
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("WinEmptyClipbrd() failed: %#x\n", WinGetLastError(g_hOs2Ab));
+ }
+ return rcExit;
+
+#elif defined(RT_OS_WINDOWS)
+ RTEXITCODE rcExit = WinOpenClipboardIfNecessary();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (!EmptyClipboard())
+ rcExit = RTMsgErrorExitFailure("EmptyClipboard() failed: %u (%#x)\n", GetLastError(), GetLastError());
+ }
+ return rcExit;
+
+#else
+ return RTMsgErrorExitFailure("ZapAllClipboardData is not implemented");
+#endif
+}
+
+
+/**
+ * Waits/delays at least @a cMsWait milliseconds.
+ *
+ * @returns Success indicator.
+ * @param cMsWait Minimum wait/delay time in milliseconds.
+ */
+static RTEXITCODE DoWait(uint32_t cMsWait)
+{
+ uint64_t const msStart = RTTimeMilliTS();
+ if (g_uVerbosity > 1)
+ RTMsgInfo("Waiting %u ms...\n", cMsWait);
+
+#if defined(RT_OS_OS2)
+ /*
+ * Arm a timer which will timeout after the desired period and
+ * quit when we've dispatched it.
+ */
+ g_fOs2TimerTicked = false;
+ if (WinStartTimer(g_hOs2Ab, g_hOs2Wnd, 1 /*idEvent*/, cMsWait + 1) != 0)
+ {
+ QMSG Msg;
+ while (WinGetMsg(g_hOs2Ab, &Msg, NULL, 0, 0))
+ {
+ WinDispatchMsg(g_hOs2Ab, &Msg);
+ if (g_fOs2TimerTicked || RTTimeMilliTS() - msStart >= cMsWait)
+ break;
+ }
+
+ if (!WinStopTimer(g_hOs2Ab, g_hOs2Wnd, 1 /*idEvent*/))
+ RTMsgWarning("WinStopTimer failed: %#x", WinGetLastError(g_hOs2Ab));
+ }
+ else
+ return RTMsgErrorExitFailure("WinStartTimer(,,,%u ms) failed: %#x", cMsWait + 1, WinGetLastError(g_hOs2Ab));
+
+#elif defined(RT_OS_WINDOWS)
+ /*
+ * Arm a timer which will timeout after the desired period and
+ * quit when we've dispatched it.
+ */
+ g_fWinTimerTicked = false;
+ if (SetTimer(g_hWinWnd, 1 /*idEvent*/, cMsWait + 1, NULL /*pfnTimerProc*/) != 0)
+ {
+ MSG Msg;
+ while (GetMessageW(&Msg, NULL, 0, 0))
+ {
+ TranslateMessage(&Msg);
+ DispatchMessageW(&Msg);
+ if (g_fWinTimerTicked || RTTimeMilliTS() - msStart >= cMsWait)
+ break;
+ }
+
+ if (!KillTimer(g_hWinWnd, 1 /*idEvent*/))
+ RTMsgWarning("KillTimer failed: %u (%#x)", GetLastError(), GetLastError());
+ }
+ else
+ return RTMsgErrorExitFailure("SetTimer(,,%u ms,) failed: %u (%#x)", cMsWait + 1, GetLastError(), GetLastError());
+
+#else
+/** @todo X11 needs to run it's message queue too, because if we're offering
+ * things on the "clipboard" we must reply to requests for them. */
+ /*
+ * Just a plain simple RTThreadSleep option (will probably not be used in the end):
+ */
+ for (;;)
+ {
+ uint64_t cMsElapsed = RTTimeMilliTS() - msStart;
+ if (cMsElapsed >= cMsWait)
+ break;
+ RTThreadSleep(cMsWait - cMsElapsed);
+ }
+#endif
+
+ if (g_uVerbosity > 2)
+ RTMsgInfo("Done waiting after %u ms.\n", RTTimeMilliTS() - msStart);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Display the usage to @a pStrm.
+ */
+static void Usage(PRTSTREAM pStrm)
+{
+ RTStrmPrintf(pStrm,
+ "usage: %s [--get <fmt> [--get ...]] [--get-file <fmt> <file> [--get-file ...]]\n"
+ " %s [--zap] [--put <fmt> <content> [--put ...]] [--put-file <fmt> <file> [--put-file ...]] [--wait <ms>]\n"
+ " %s [--check <fmt> <expected> [--check ...]] [--check-file <fmt> <file> [--check-file ...]]\n"
+ " [--check-no <fmt> [--check-no ...]]\n"
+ , RTProcShortName(), RTProcShortName(), RTProcShortName());
+ RTStrmPrintf(pStrm, "\n");
+ RTStrmPrintf(pStrm, "Actions/Options:\n");
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
+ {
+ const char *pszHelp;
+ switch (g_aCmdOptions[i].iShort)
+ {
+ case 'l': pszHelp = "List the clipboard content."; break;
+ case 'g': pszHelp = "Get given clipboard format and writes it to standard output."; break;
+ case 'G': pszHelp = "Get given clipboard format and writes it to the specified file."; break;
+ case 'p': pszHelp = "Puts given format and content on the clipboard."; break;
+ case 'P': pszHelp = "Puts given format and file content on the clipboard."; break;
+ case 'c': pszHelp = "Checks that the given format and content matches the clipboard."; break;
+ case 'C': pszHelp = "Checks that the given format and file content matches the clipboard."; break;
+ case 'n': pszHelp = "Checks that the given format is not on the clipboard."; break;
+ case 'z': pszHelp = "Zaps the clipboard content."; break;
+#ifdef MULTI_TARGET_CLIPBOARD
+ case 't': pszHelp = "Selects the target clipboard."; break;
+#endif
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ case 'k': pszHelp = "Closes the clipboard if open (win,os2)."; break;
+#endif
+ case 'w': pszHelp = "Waits a given number of milliseconds before continuing."; break;
+ case 'v': pszHelp = "More verbose execution."; break;
+ case 'q': pszHelp = "Quiet execution."; break;
+ case 'h': pszHelp = "Displays this help and exit"; break;
+ case 'V': pszHelp = "Displays the program revision"; break;
+
+ default:
+ pszHelp = "Option undocumented";
+ break;
+ }
+ if ((unsigned)g_aCmdOptions[i].iShort < 127U)
+ {
+ char szOpt[64];
+ RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
+ RTStrmPrintf(pStrm, " %-19s %s\n", szOpt, pszHelp);
+ }
+ else
+ RTStrmPrintf(pStrm, " %-19s %s\n", g_aCmdOptions[i].pszLong, pszHelp);
+ }
+ RTStrmPrintf(pStrm,
+ "\n"
+ "Note! Options are processed in the order they are given.\n");
+
+ RTStrmPrintf(pStrm, "\nFormats:\n");
+ for (size_t i = 0; i < RT_ELEMENTS(g_aFormats); i++)
+ RTStrmPrintf(pStrm, " %-12s: %s\n", g_aFormats[i].pszName, g_aFormats[i].pszDesc);
+
+#ifdef MULTI_TARGET_CLIPBOARD
+ RTStrmPrintf(pStrm, "\nTarget:\n");
+ for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++)
+ RTStrmPrintf(pStrm, " %-12s: %s\n", g_aTargets[i].pszName, g_aTargets[i].pszDesc);
+#endif
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init IPRT.
+ */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Host specific init.
+ */
+#ifdef RT_OS_DARWIN
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+#elif defined(RT_OS_OS2)
+ RTEXITCODE rcExit = CuOs2Init();
+#elif defined(RT_OS_WINDOWS)
+ RTEXITCODE rcExit = CuWinInit();
+#else
+ RTEXITCODE rcExit = CuX11Init();
+#endif
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Process options (in order).
+ */
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS;
+ switch (rc)
+ {
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ case 'k':
+ rcExit2 = CuCloseClipboard();
+ break;
+#endif
+
+ case 'l':
+ rcExit2 = ListClipboardContent();
+ break;
+
+ case 'g':
+ {
+ PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
+ if (pFmtDesc)
+ rcExit2 = ClipboardContentToStdOut(pFmtDesc);
+ else
+ rcExit2 = RTEXITCODE_FAILURE;
+ break;
+ }
+
+ case 'G':
+ {
+ PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
+ if (pFmtDesc)
+ {
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
+ if (RT_SUCCESS(rc))
+ rcExit2 = ClipboardContentToFile(pFmtDesc, ValueUnion.psz);
+ else
+ return RTMsgErrorExitFailure("No filename given with --get-file");
+ }
+ else
+ rcExit2 = RTEXITCODE_FAILURE;
+ break;
+ }
+
+ case 'p':
+ {
+ PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
+ if (pFmtDesc)
+ {
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
+ if (RT_SUCCESS(rc))
+ rcExit2 = PutStringOnClipboard(pFmtDesc, ValueUnion.psz);
+ else
+ return RTMsgErrorExitFailure("No data string given with --put");
+ }
+ else
+ rcExit2 = RTEXITCODE_FAILURE;
+ break;
+ }
+
+ case 'P':
+ {
+ PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
+ if (pFmtDesc)
+ {
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
+ if (RT_SUCCESS(rc))
+ rcExit2 = PutFileOnClipboard(pFmtDesc, ValueUnion.psz);
+ else
+ return RTMsgErrorExitFailure("No filename given with --put-file");
+ }
+ else
+ rcExit2 = RTEXITCODE_FAILURE;
+ break;
+ }
+
+ case 'c':
+ {
+ PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
+ if (pFmtDesc)
+ {
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
+ if (RT_SUCCESS(rc))
+ rcExit2 = CheckStringAgainstClipboard(pFmtDesc, ValueUnion.psz);
+ else
+ return RTMsgErrorExitFailure("No data string given with --check");
+ }
+ else
+ rcExit2 = RTEXITCODE_FAILURE;
+ break;
+ }
+
+ case 'C':
+ {
+ PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
+ if (pFmtDesc)
+ {
+ rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_STRING);
+ if (RT_SUCCESS(rc))
+ rcExit2 = CheckFileAgainstClipboard(pFmtDesc, ValueUnion.psz);
+ else
+ return RTMsgErrorExitFailure("No filename given with --check-file");
+ }
+ else
+ rcExit2 = RTEXITCODE_FAILURE;
+ break;
+ }
+
+ case 'n':
+ {
+ PCCLIPUTILFORMAT pFmtDesc = GetFormatDesc(ValueUnion.psz);
+ if (pFmtDesc)
+ rcExit2 = CheckFormatNotOnClipboard(pFmtDesc);
+ else
+ rcExit2 = RTEXITCODE_FAILURE;
+ break;
+ }
+
+
+ case 'z':
+ rcExit2 = ZapAllClipboardData();
+ break;
+
+#ifdef MULTI_TARGET_CLIPBOARD
+ case 't':
+ {
+ CLIPUTILTARGET *pNewTarget = NULL;
+ for (size_t i = 0; i < RT_ELEMENTS(g_aTargets); i++)
+ if (strcmp(ValueUnion.psz, g_aTargets[i].pszName) == 0)
+ {
+ pNewTarget = &g_aTargets[i];
+ break;
+ }
+ if (!pNewTarget)
+ return RTMsgErrorExitFailure("Unknown target '%s'", ValueUnion.psz);
+ if (pNewTarget != g_pTarget && g_uVerbosity > 0)
+ RTMsgInfo("Switching from '%s' to '%s'\n", g_pTarget->pszName, pNewTarget->pszName);
+ g_pTarget = pNewTarget;
+ break;
+ }
+#endif
+
+ case 'w':
+ rcExit2 = DoWait(ValueUnion.u32);
+ break;
+
+ case 'q':
+ g_uVerbosity = 0;
+ break;
+
+ case 'v':
+ g_uVerbosity++;
+ break;
+
+ case 'h':
+ Usage(g_pStdOut);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ {
+ char szRev[] = "$Revision: 155244 $";
+ szRev[RT_ELEMENTS(szRev) - 2] = '\0';
+ RTPrintf(RTStrStrip(strchr(szRev, ':') + 1));
+ return RTEXITCODE_SUCCESS;
+ }
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+
+ if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+
+ /*
+ * Host specific cleanup.
+ */
+#if defined(RT_OS_OS2)
+ RTEXITCODE rcExit2 = CuOs2Term();
+#elif defined(RT_OS_WINDOWS)
+ RTEXITCODE rcExit2 = CuWinTerm();
+#else
+ RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS;
+#endif
+ if (rcExit2 != RTEXITCODE_SUCCESS && rcExit != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+
+ return rcExit;
+}
+