/* $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;
}

