/** $Id: VBoxServiceClipboard-os2.cpp $ */
/** @file
 * VBoxService - Guest Additions Clipboard Service, OS/2.
 */

/*
 * Copyright (C) 2007-2012 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#define INCL_BASE
#define INCL_PM
#define INCL_ERRORS
#include <os2.h>

#include <iprt/thread.h>
#include <iprt/string.h>
#include <iprt/semaphore.h>
#include <iprt/time.h>
#include <iprt/mem.h>
#include <iprt/param.h>
#include <iprt/assert.h>
#include <iprt/asm.h>
#include <VBox/VBoxGuestLib.h>
#include <VBox/HostServices/VBoxClipboardSvc.h>
#include "VBoxServiceInternal.h"


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/** Header for Odin32 specific clipboard entries.
 * (Used to get the correct size of the data.)
 */
typedef struct _Odin32ClipboardHeader
{
    /** magic number */
    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"


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/

/** The control thread (main) handle.
 * Only used to avoid some queue creation trouble. */
static RTTHREAD g_ThreadCtrl = NIL_RTTHREAD;
/** The HAB of the control thread (main). */
static HAB g_habCtrl = NULLHANDLE;
/** The HMQ of the control thread (main). */
static HMQ g_hmqCtrl = NULLHANDLE;

/** The Listener thread handle. */
static RTTHREAD g_ThreadListener = NIL_RTTHREAD;
/** The HAB of the listener thread. */
static HAB g_habListener = NULLHANDLE;
/** The HMQ of the listener thread. */
static HMQ g_hmqListener = NULLHANDLE;
/** Indicator that gets set if the listener thread is successfully initialized. */
static bool volatile g_fListenerOkay = false;

/** The HAB of the worker thread. */
static HAB g_habWorker = NULLHANDLE;
/** The HMQ of the worker thread. */
static HMQ g_hmqWorker = NULLHANDLE;
/** The object window handle. */
static HWND g_hwndWorker = NULLHANDLE;
/** The timer id returned by WinStartTimer. */
static ULONG g_idWorkerTimer = ~0UL;
/** The state of the clipboard.
 * @remark I'm trying out the 'k' prefix from the mac here, bear with me.  */
static enum
{
    /** The clipboard hasn't been initialized yet. */
    kClipboardState_Uninitialized = 0,
    /** WinSetClipbrdViewer call in progress, ignore WM_DRAWCLIPBOARD. */
    kClipboardState_SettingViewer,
    /** We're monitoring the clipboard as a viewer. */
    kClipboardState_Viewer,
    /** We're monitoring the clipboard using polling.
     * This usually means something is wrong... */
    kClipboardState_Polling,
    /** We're destroying the clipboard content, ignore WM_DESTROYCLIPBOARD. */
    kClipboardState_Destroying,
    /** We're owning the clipboard (i.e. we have data on it). */
    kClipboardState_Owner
} g_enmState = kClipboardState_Uninitialized;
/** Set if the clipboard was empty the last time we polled it. */
static bool g_fEmptyClipboard = false;

/** A clipboard format atom for the dummy clipboard data we insert
 * watching for clipboard changes. If this format is found on the
 * clipboard, the empty clipboard function has not been called
 * since we last polled it. */
static ATOM g_atomNothingChanged = 0;

/** The clipboard connection client ID. */
static uint32_t g_u32ClientId;
/** Odin32 CF_UNICODETEXT. See user32.cpp. */
static ATOM g_atomOdin32UnicodeText = 0;
/** Odin32 CF_UNICODETEXT. See user32.cpp. */
#define SZFMT_ODIN32_UNICODETEXT    (PCSZ)"Odin32 UnicodeText"




/** @copydoc VBOXSERVICE::pfnPreInit */
static DECLCALLBACK(int) VBoxServiceClipboardOS2PreInit(void)
{
    return VINF_SUCCESS;
}


/** @copydoc VBOXSERVICE::pfnOption */
static DECLCALLBACK(int) VBoxServiceClipboardOS2Option(const char **ppszShort, int argc, char **argv, int *pi)
{
    NOREF(ppszShort);
    NOREF(argc);
    NOREF(argv);
    NOREF(pi);

    return -1;
}


/** @copydoc VBOXSERVICE::pfnInit */
static DECLCALLBACK(int) VBoxServiceClipboardOS2Init(void)
{
    int rc = VERR_GENERAL_FAILURE;
    g_ThreadCtrl = RTThreadSelf();

    /*
     * Make PM happy.
     */
    PPIB pPib;
    PTIB pTib;
    DosGetInfoBlocks(&pTib, &pPib);
    pPib->pib_ultype = 3; /* PM session type */

    /*
     * Since we have to send shutdown messages and such from the
     * service controller (main) thread, create a HAB and HMQ for it.
     */
    g_habCtrl = WinInitialize(0);
    if (g_habCtrl  == NULLHANDLE)
    {
        VBoxServiceError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE));
        return VERR_GENERAL_FAILURE;
    }
    g_hmqCtrl = WinCreateMsgQueue(g_habCtrl, 0);
    if (g_hmqCtrl != NULLHANDLE)
    {
        WinCancelShutdown(g_hmqCtrl, TRUE); /* We don't care about shutdown */

        /*
         * Create the 'nothing-changed' format.
         */
        g_atomNothingChanged = WinAddAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
        LONG lLastError = WinGetLastError(g_habCtrl);
        if (g_atomNothingChanged == 0)
            g_atomNothingChanged = WinFindAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
        if (g_atomNothingChanged)
        {
            /*
             * Connect to the clipboard service.
             */
            VBoxServiceVerbose(4, "clipboard: connecting\n");
            rc = VbglR3ClipboardConnect(&g_u32ClientId);
            if (RT_SUCCESS(rc))
            {
                /*
                 * Create any extra clipboard type atoms, like the odin unicode text.
                 */
                g_atomOdin32UnicodeText = WinAddAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT);
                lLastError = WinGetLastError(g_habCtrl);
                if (g_atomOdin32UnicodeText == 0)
                    g_atomOdin32UnicodeText = WinFindAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT);
                if (g_atomOdin32UnicodeText == 0)
                    VBoxServiceError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
                                     lLastError, WinGetLastError(g_habCtrl));

                VBoxServiceVerbose(2, "g_u32ClientId=%RX32 g_atomNothingChanged=%#x g_atomOdin32UnicodeText=%#x\n",
                                   g_u32ClientId, g_atomNothingChanged, g_atomOdin32UnicodeText);
                return VINF_SUCCESS;
            }

            VBoxServiceError("Failed to connect to the clipboard service, rc=%Rrc!\n", rc);
        }
        else
            VBoxServiceError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
                             lLastError, WinGetLastError(g_habCtrl));
    }
    else
        VBoxServiceError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl));
    WinTerminate(g_habCtrl);
    return rc;
}


/**
 * Check that we're still the view / try make us the viewer.
 */
static void VBoxServiceClipboardOS2PollViewer(void)
{
    const int iOrgState = g_enmState;

    HWND hwndClipboardViewer = WinQueryClipbrdViewer(g_habWorker);
    if (hwndClipboardViewer == g_hwndWorker)
        return;

    if (hwndClipboardViewer == NULLHANDLE)
    {
        /* The API will send a WM_DRAWCLIPBOARD message before returning. */
        g_enmState = kClipboardState_SettingViewer;
        if (WinSetClipbrdViewer(g_habWorker, g_hwndWorker))
            g_enmState = kClipboardState_Viewer;
        else
            g_enmState = kClipboardState_Polling;
    }
    else
        g_enmState = kClipboardState_Polling;
    if ((int)g_enmState != iOrgState)
    {
        if (g_enmState == kClipboardState_Viewer)
            VBoxServiceVerbose(3, "clipboard: viewer\n");
        else
            VBoxServiceVerbose(3, "clipboard: poller\n");
    }
}


/**
 * Advertise the formats available from the host.
 */
static void VBoxServiceClipboardOS2AdvertiseHostFormats(uint32_t fFormats)
{
    /*
     * Open the clipboard and switch to 'destruction' mode.
     * Make sure we stop being viewer. Temporarily also make sure we're
     * not the owner so that PM won't send us any WM_DESTROYCLIPBOARD message.
     */
    if (WinOpenClipbrd(g_habWorker))
    {
        if (g_enmState == kClipboardState_Viewer)
            WinSetClipbrdViewer(g_habWorker, NULLHANDLE);
        if (g_enmState == kClipboardState_Owner)
            WinSetClipbrdOwner(g_habWorker, NULLHANDLE);

        g_enmState = kClipboardState_Destroying;
        if (WinEmptyClipbrd(g_habWorker))
        {
            /*
             * Take clipboard ownership.
             */
            if (WinSetClipbrdOwner(g_habWorker, g_hwndWorker))
            {
                g_enmState = kClipboardState_Owner;

                /*
                 * Do the format advertising.
                 */
                if (fFormats & (VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT/* | VBOX_SHARED_CLIPBOARD_FMT_HTML ?? */))
                {
                    if (!WinSetClipbrdData(g_habWorker, 0, CF_TEXT, CFI_POINTER))
                        VBoxServiceError("WinSetClipbrdData(,,CF_TEXT,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
                    if (    g_atomOdin32UnicodeText
                        &&  !WinSetClipbrdData(g_habWorker, 0, g_atomOdin32UnicodeText, CFI_POINTER))
                        VBoxServiceError("WinSetClipbrdData(,,g_atomOdin32UnicodeText,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
                }
                if (fFormats & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
                {
                    /** @todo bitmaps */
                }
            }
            else
            {
                VBoxServiceError("WinSetClipbrdOwner failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
                g_enmState = kClipboardState_Polling;
            }
        }
        else
        {
            VBoxServiceError("WinEmptyClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
            g_enmState = kClipboardState_Polling;
        }

        if (g_enmState == kClipboardState_Polling)
        {
            g_fEmptyClipboard = true;
            VBoxServiceClipboardOS2PollViewer();
        }

        WinCloseClipbrd(g_habWorker);
    }
    else
        VBoxServiceError("VBoxServiceClipboardOS2AdvertiseHostFormats: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
}


static void *VBoxServiceClipboardOs2ConvertToOdin32(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
{
    PVOID pvPM = NULL;
    APIRET rc = DosAllocSharedMem(&pvPM, NULL, cb + sizeof(CLIPHEADER), OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
    if (rc)
    {
        PCLIPHEADER pHdr = (PCLIPHEADER)pvPM;
        memcpy(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic));
        pHdr->cbData = cb;
        if (usFmt == g_atomOdin32UnicodeText)
            pHdr->uFormat = usFmt;
        else
            AssertFailed();
        memcpy(pHdr + 1, pv, cb);
    }
    else
    {
        VBoxServiceError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), rc);
        pvPM = NULL;
    }
    return pvPM;
}


static void *VBoxServiceClipboardOs2ConvertToPM(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
{
    void *pvPM = NULL;

    /*
     * The Odin32 stuff is simple, we just assume windows data from the host
     * and all we need to do is add the header.
     */
    if (    usFmt
        &&  (   usFmt == g_atomOdin32UnicodeText
             /* || usFmt == ...*/
            )
       )
        pvPM = VBoxServiceClipboardOs2ConvertToOdin32(fFormat, usFmt, pv, cb);
    else if (usFmt == CF_TEXT)
    {
        /*
         * Convert the unicode text to the current ctype locale.
         *
         * Note that we probably should be using the current PM or DOS codepage
         * here instead of the LC_CTYPE one which iconv uses by default.
         * -lazybird
         */
        Assert(fFormat & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
        char *pszUtf8;
        int rc = RTUtf16ToUtf8((PCRTUTF16)pv, &pszUtf8);
        if (RT_SUCCESS(rc))
        {
            char *pszLocale;
            rc = RTStrUtf8ToCurrentCP(&pszLocale, pszUtf8);
            if (RT_SUCCESS(rc))
            {
                size_t cbPM = strlen(pszLocale) + 1;
                APIRET orc = DosAllocSharedMem(&pvPM, NULL, cbPM, OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
                if (orc == NO_ERROR)
                    memcpy(pvPM, pszLocale, cbPM);
                else
                {
                    VBoxServiceError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), orc);
                    pvPM = NULL;
                }
                RTStrFree(pszLocale);
            }
            else
                VBoxServiceError("RTStrUtf8ToCurrentCP() -> %Rrc\n", rc);
            RTStrFree(pszUtf8);
        }
        else
            VBoxServiceError("RTUtf16ToUtf8() -> %Rrc\n", rc);
    }

    return pvPM;
}


/**
 * Tries to deliver an advertised host format.
 *
 * @param   usFmt       The PM format name.
 *
 * @remark  We must not try open the clipboard here because WM_RENDERFMT is a
 *          request send synchronously by someone who has already opened the
 *          clipboard. We would enter a deadlock trying to open it here.
 *
 */
static void VBoxServiceClipboardOS2RenderFormat(USHORT usFmt)
{
    bool fSucceeded = false;

    /*
     * Determine which format.
     */
    uint32_t fFormat;
    if (    usFmt == CF_TEXT
        ||  usFmt == g_atomOdin32UnicodeText)
        fFormat = VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
    else /** @todo bitmaps */
        fFormat = 0;
    if (fFormat)
    {
        /*
         * Query the data from the host.
         * This might require two iterations because of buffer guessing.
         */
        uint32_t cb = _4K;
        uint32_t cbAllocated = cb;
        int      rc = VERR_NO_MEMORY;
        void    *pv = RTMemPageAllocZ(cbAllocated);
        if (pv)
        {
            VBoxServiceVerbose(4, "clipboard: reading host data (%#x)\n", fFormat);
            rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb);
            if (rc == VINF_BUFFER_OVERFLOW)
            {
                RTMemPageFree(pv, cbAllocated);
                cbAllocated = cb = RT_ALIGN_32(cb, PAGE_SIZE);
                pv = RTMemPageAllocZ(cbAllocated);
                rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb);
            }
            if (RT_FAILURE(rc))
                RTMemPageFree(pv, cbAllocated);
        }
        if (RT_SUCCESS(rc))
        {
            VBoxServiceVerbose(4, "clipboard: read %u bytes\n", cb);

            /*
             * Convert the host clipboard data to PM clipboard data and set it.
             */
            PVOID pvPM = VBoxServiceClipboardOs2ConvertToPM(fFormat, usFmt, pv, cb);
            if (pvPM)
            {
                if (WinSetClipbrdData(g_habWorker, (ULONG)pvPM, usFmt, CFI_POINTER))
                    fSucceeded = true;
                else
                {
                    VBoxServiceError("VBoxServiceClipboardOS2RenderFormat: WinSetClipbrdData(,%p,%#x, CF_POINTER) failed, lasterror=%lx\n",
                                     pvPM, usFmt, WinGetLastError(g_habWorker));
                    DosFreeMem(pvPM);
                }
            }
            RTMemPageFree(pv, cbAllocated);
        }
        else
            VBoxServiceError("VBoxServiceClipboardOS2RenderFormat: Failed to query / allocate data. rc=%Rrc cb=%#RX32\n", rc, cb);
    }

    /*
     * Empty the clipboard on failure so we don't end up in any loops.
     */
    if (!fSucceeded)
    {
        WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
        g_enmState = kClipboardState_Destroying;
        WinEmptyClipbrd(g_habWorker);
        g_enmState = kClipboardState_Polling;
        g_fEmptyClipboard = true;
        VBoxServiceClipboardOS2PollViewer();
    }
}


static void VBoxServiceClipboardOS2SendDataToHost(uint32_t fFormat)
{
    if (WinOpenClipbrd(g_habWorker))
    {
        PRTUTF16 pwszFree = NULL;
        void *pv = NULL;
        uint32_t cb = 0;

        if (fFormat & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
        {
            /* Got any odin32 unicode text? */
            PVOID pvPM;
            PCLIPHEADER pHdr = (PCLIPHEADER)WinQueryClipbrdData(g_habWorker, g_atomOdin32UnicodeText);
            if (    pHdr
                &&  !memcmp(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic)))
            {
                pv = pHdr + 1;
                cb = pHdr->cbData;
            }

            /* Got any CF_TEXT? */
            if (    !pv
                &&  (pvPM = (PVOID)WinQueryClipbrdData(g_habWorker, CF_TEXT)) != NULL)
            {
                char *pszUtf8;
                int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)pvPM);
                if (RT_SUCCESS(rc))
                {
                    PRTUTF16 pwsz;
                    rc = RTStrToUtf16(pszUtf8, &pwsz);
                    if (RT_SUCCESS(rc))
                    {
                        pv = pwszFree = pwsz;
                        cb = (RTUtf16Len(pwsz) + 1) * sizeof(RTUTF16);
                    }
                    RTStrFree(pszUtf8);
                }
            }
        }
        if (!pv)
            VBoxServiceError("VBoxServiceClipboardOS2SendDataToHost: couldn't find data for %#x\n", fFormat);

        /*
         * Now, sent whatever we've got to the host (it's waiting).
         */
        VBoxServiceVerbose(4, "clipboard: writing %pv/%#d (fFormat=%#x)\n", pv, cb, fFormat);
        VbglR3ClipboardWriteData(g_u32ClientId, fFormat, pv, cb);
        RTUtf16Free(pwszFree);

        WinCloseClipbrd(g_habWorker);
    }
    else
    {
        VBoxServiceError("VBoxServiceClipboardOS2SendDataToHost: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
        VBoxServiceVerbose(4, "clipboard: writing NULL/0 (fFormat=%x)\n", fFormat);
        VbglR3ClipboardWriteData(g_u32ClientId, fFormat, NULL, 0);
    }
}


/**
 * Figure out what's on the clipboard and report it to the host.
 */
static void VBoxServiceClipboardOS2ReportFormats(void)
{
    uint32_t fFormats = 0;
    ULONG ulFormat = 0;
    while ((ulFormat = WinEnumClipbrdFmts(g_habWorker, ulFormat)) != 0)
    {
        if (    ulFormat == CF_TEXT
            ||  ulFormat == g_atomOdin32UnicodeText)
            fFormats |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
        /** @todo else bitmaps and stuff. */
    }
    VBoxServiceVerbose(4, "clipboard: reporting fFormats=%#x\n", fFormats);
    VbglR3ClipboardReportFormats(g_u32ClientId, fFormats);
}


/**
 * Poll the clipboard for changes.
 *
 * This is called both when we're the viewer and when we're
 * falling back to polling. If something has changed it will
 * notify the host.
 */
static void VBoxServiceClipboardOS2Poll(void)
{
    if (WinOpenClipbrd(g_habWorker))
    {
        /*
         * If our dummy is no longer there, something has actually changed,
         * unless the clipboard is really empty.
         */
        ULONG fFmtInfo;
        if (!WinQueryClipbrdFmtInfo(g_habWorker, g_atomNothingChanged, &fFmtInfo))
        {
            if (WinEnumClipbrdFmts(g_habWorker, 0) != 0)
            {
                g_fEmptyClipboard = false;
                VBoxServiceClipboardOS2ReportFormats();

                /* inject the dummy */
                PVOID pv;
                APIRET rc = DosAllocSharedMem(&pv, NULL, 1, OBJ_GIVEABLE | OBJ_GETTABLE | PAG_READ | PAG_WRITE | PAG_COMMIT);
                if (rc == NO_ERROR)
                {
                    if (WinSetClipbrdData(g_habWorker, (ULONG)pv, g_atomNothingChanged, CFI_POINTER))
                        VBoxServiceVerbose(4, "clipboard: Added dummy item.\n");
                    else
                    {
                        VBoxServiceError("VBoxServiceClipboardOS2Poll: WinSetClipbrdData failed, lasterr=%#lx\n", WinGetLastError(g_habWorker));
                        DosFreeMem(pv);
                    }
                }
                else
                    VBoxServiceError("VBoxServiceClipboardOS2Poll: DosAllocSharedMem(,,1,) -> %ld\n", rc);
            }
            else if (!g_fEmptyClipboard)
            {
                g_fEmptyClipboard = true;
                VBoxServiceVerbose(3, "Reporting empty clipboard\n");
                VbglR3ClipboardReportFormats(g_u32ClientId, 0);
            }
        }
        WinCloseClipbrd(g_habWorker);
    }
    else
        VBoxServiceError("VBoxServiceClipboardOS2Poll: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
}


/**
 * The clipboard we owned was destroyed by someone else.
 */
static void VBoxServiceClipboardOS2Destroyed(void)
{
    /* make sure we're no longer the owner. */
    if (WinQueryClipbrdOwner(g_habWorker) == g_hwndWorker)
        WinSetClipbrdOwner(g_habWorker, NULLHANDLE);

    /* switch to polling state and notify the host. */
    g_enmState = kClipboardState_Polling;
    g_fEmptyClipboard = true;
    VBoxServiceVerbose(3, "Reporting empty clipboard\n");
    VbglR3ClipboardReportFormats(g_u32ClientId, 0);

    VBoxServiceClipboardOS2PollViewer();
}


/**
 * 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 VBoxServiceClipboardOS2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    if (msg != WM_TIMER)
        VBoxServiceVerbose(6, "VBoxServiceClipboardOS2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2);

    switch (msg)
    {
        /*
         * Handle the two system defined messages for object windows.
         *
         * We'll just use the CREATE/DESTROY message to create that timer we're
         * using for the viewer checks and polling fallback.
         */
        case WM_CREATE:
            g_idWorkerTimer = WinStartTimer(g_habWorker, hwnd, 1 /* id */, 1000 /* 1 second */);
            g_fEmptyClipboard = true;
            g_enmState = kClipboardState_Polling;
            return NULL; /* FALSE(/NULL) == Continue*/

        case WM_DESTROY:
            WinStopTimer(g_habWorker, hwnd, g_idWorkerTimer);
            g_idWorkerTimer = ~0UL;
            g_hwndWorker = NULLHANDLE;
            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:
            if (g_enmState == kClipboardState_SettingViewer)
                break;
            AssertMsgBreak(g_enmState == kClipboardState_Viewer, ("g_enmState=%d\n", g_enmState));
            VBoxServiceClipboardOS2Poll();
            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:
            if (g_enmState == kClipboardState_Destroying)
                break; /* it's us doing the replacing, ignore. */
            AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState));
            VBoxServiceClipboardOS2Destroyed();
            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:
            AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState));
            VBoxServiceClipboardOS2RenderFormat(SHORT1FROMMP(mp1));
            break;

        /*
         * Clipboard owner message - we're about to quit and should render all formats.
         *
         * However, because we're lazy, we'll just ASSUME that since we're quitting
         * we're probably about to shutdown or something and there is no point in
         * doing anything here except for emptying the clipboard and removing
         * ourselves as owner. Any failures at this point are silently ignored.
         */
        case WM_RENDERALLFMTS:
            WinOpenClipbrd(g_habWorker);
            WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
            g_enmState = kClipboardState_Destroying;
            WinEmptyClipbrd(g_habWorker);
            g_enmState = kClipboardState_Polling;
            g_fEmptyClipboard = true;
            WinCloseClipbrd(g_habWorker);
            break;

        /*
         * Listener message - the host has new formats to offer.
         */
        case WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS:
            VBoxServiceClipboardOS2AdvertiseHostFormats(LONGFROMMP(mp1));
            break;

        /*
         * Listener message - the host wish to read our clipboard data.
         */
        case WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA:
            VBoxServiceClipboardOS2SendDataToHost(LONGFROMMP(mp1));
            break;

        /*
         * This is just a fallback polling strategy in case some other
         * app is trying to view the clipboard too. We also use this
         * to try recover from errors.
         *
         * Because the way the clipboard service works, we have to monitor
         * it all the time and cannot get away with simpler solutions like
         * synergy is employing (basically checking upon entering and leaving
         * a desktop).
         */
        case WM_TIMER:
            if (    g_enmState != kClipboardState_Viewer
                &&  g_enmState != kClipboardState_Polling)
                break;

            /* Lost the position as clipboard viewer?*/
            if (g_enmState == kClipboardState_Viewer)
            {
                if (WinQueryClipbrdViewer(g_habWorker) == hwnd)
                    break;
                g_enmState = kClipboardState_Polling;
            }

            /* poll for changes */
            VBoxServiceClipboardOS2Poll();
            VBoxServiceClipboardOS2PollViewer();
            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;
    }
    return NULL;
}


/**
 * The listener thread.
 *
 * This thread is dedicated to listening for host messages and forwarding
 * these to the worker thread (using PM).
 *
 * The thread will set g_fListenerOkay and signal its user event when it has
 * completed initialization. In the case of init failure g_fListenerOkay will
 * not be set.
 *
 * @returns Init error code or VINF_SUCCESS.
 * @param   ThreadSelf  Our thread handle.
 * @param   pvUser      Pointer to the clipboard service shutdown indicator.
 */
static DECLCALLBACK(int) VBoxServiceClipboardOS2Listener(RTTHREAD ThreadSelf, void *pvUser)
{
    bool volatile *pfShutdown = (bool volatile *)pvUser;
    int rc = VERR_GENERAL_FAILURE;
    VBoxServiceVerbose(3, "VBoxServiceClipboardOS2Listener: ThreadSelf=%RTthrd\n", ThreadSelf);

    g_habListener = WinInitialize(0);
    if (g_habListener != NULLHANDLE)
    {
        g_hmqListener = WinCreateMsgQueue(g_habListener, 0);
        if (g_hmqListener != NULLHANDLE)
        {
            WinCancelShutdown(g_hmqListener, TRUE); /* We don't care about shutdown */

            /*
             * Tell the worker thread that we're good.
             */
            rc = VINF_SUCCESS;
            ASMAtomicXchgBool(&g_fListenerOkay, true);
            RTThreadUserSignal(ThreadSelf);
            VBoxServiceVerbose(3, "VBoxServiceClipboardOS2Listener: Started successfully\n");

            /*
             * Loop until termination is requested.
             */
            bool fQuit = false;
            while (!*pfShutdown && !fQuit)
            {
                uint32_t Msg;
                uint32_t fFormats;
                rc = VbglR3ClipboardGetHostMsg(g_u32ClientId, &Msg, &fFormats);
                if (RT_SUCCESS(rc))
                {
                    VBoxServiceVerbose(3, "VBoxServiceClipboardOS2Listener: Msg=%#x  fFormats=%#x\n", Msg, fFormats);
                    switch (Msg)
                    {
                        /*
                         * The host has announced available clipboard formats.
                         * Forward the information to the window, so it can later
                         * respond do WM_RENDERFORMAT message.
                         */
                        case VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS:
                            if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS,
                                            MPFROMLONG(fFormats), 0))
                                VBoxServiceError("WinPostMsg(%lx, FORMATS,,) failed, lasterr=%#lx\n",
                                                 g_hwndWorker, WinGetLastError(g_habListener));
                            break;

                        /*
                         * The host needs data in the specified format.
                         */
                        case VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA:
                            if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
                                            MPFROMLONG(fFormats), 0))
                                VBoxServiceError("WinPostMsg(%lx, READ_DATA,,) failed, lasterr=%#lx\n",
                                                 g_hwndWorker, WinGetLastError(g_habListener));
                            break;

                        /*
                         * The host is terminating.
                         */
                        case VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT:
                            fQuit = true;
                            break;

                        default:
                            VBoxServiceVerbose(1, "VBoxServiceClipboardOS2Listener: Unknown message %RU32\n", Msg);
                            break;
                    }
                }
                else
                {
                    if (*pfShutdown)
                        break;
                    VBoxServiceError("VbglR3ClipboardGetHostMsg failed, rc=%Rrc\n", rc);
                    RTThreadSleep(1000);
                }
            } /* the loop */

            WinDestroyMsgQueue(g_hmqListener);
        }
        WinTerminate(g_habListener);
        g_habListener = NULLHANDLE;
    }

    /* Signal our semaphore to make the worker catch on. */
    RTThreadUserSignal(ThreadSelf);
    VBoxServiceVerbose(3, "VBoxServiceClipboardOS2Listener: terminating, rc=%Rrc\n", rc);
    return rc;
}


/** @copydoc VBOXSERVICE::pfnWorker */
static DECLCALLBACK(int) VBoxServiceClipboardOS2Worker(bool volatile *pfShutdown)
{
    int rc = VERR_GENERAL_FAILURE;

    /*
     * Standard PM init.
     */
    g_habWorker = RTThreadSelf() != g_ThreadCtrl ? WinInitialize(0) : g_habCtrl;
    if (g_habWorker != NULLHANDLE)
    {
        g_hmqWorker = RTThreadSelf() != g_ThreadCtrl ? WinCreateMsgQueue(g_habWorker, 0) : g_hmqCtrl;
        if (g_hmqWorker != NULLHANDLE)
        {
            if (g_hmqWorker != g_hmqCtrl)
                WinCancelShutdown(g_hmqWorker, TRUE); /* We don't care about shutdown */

            /*
             * Create the object window.
             */
            if (WinRegisterClass(g_habWorker, (PCSZ)"VBoxServiceClipboardClass", VBoxServiceClipboardOS2WinProc, 0, 0))
            {
                g_hwndWorker = WinCreateWindow(HWND_OBJECT,                             /* hwndParent */
                                               (PCSZ)"VBoxServiceClipboardClass",       /* pszClass */
                                               (PCSZ)"VirtualBox Clipboard Service",    /* pszName */
                                               0,                                       /* flStyle */
                                               0, 0, 0, 0,                              /* x, y, cx, cy */
                                               NULLHANDLE,                              /* hwndOwner */
                                               HWND_BOTTOM,                             /* hwndInsertBehind */
                                               42,                                      /* id */
                                               NULL,                                    /* pCtlData */
                                               NULL);                                   /* pPresParams */
                if (g_hwndWorker != NULLHANDLE)
                {
                    VBoxServiceVerbose(3, "g_hwndWorker=%#lx g_habWorker=%#lx g_hmqWorker=%#lx\n", g_hwndWorker, g_habWorker, g_hmqWorker);

                    /*
                     * Create the listener thread.
                     */
                    g_fListenerOkay = false;
                    rc = RTThreadCreate(&g_ThreadListener, VBoxServiceClipboardOS2Listener, (void *)pfShutdown, 0,
                                        RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CLIPLISTEN");
                    if (RT_SUCCESS(rc))
                    {
                        RTThreadUserWait(g_ThreadListener, 30*1000);
                        RTThreadUserReset(g_ThreadListener);
                        if (!g_fListenerOkay)
                            RTThreadWait(g_ThreadListener, 60*1000, NULL);
                        if (g_fListenerOkay)
                        {
                            /*
                             * Tell the control thread that it can continue
                             * spawning services.
                             */
                            RTThreadUserSignal(RTThreadSelf());

                            /*
                             * The PM event pump.
                             */
                            VBoxServiceVerbose(2, "clipboard: Entering PM message loop.\n");
                            rc = VINF_SUCCESS;
                            QMSG qmsg;
                            while (WinGetMsg(g_habWorker, &qmsg, NULLHANDLE, NULLHANDLE, 0))
                            {
                                if (qmsg.msg != WM_TIMER)
                                    VBoxServiceVerbose(6, "WinGetMsg -> hwnd=%p msg=%#x mp1=%p mp2=%p time=%#x ptl=%d,%d rsrv=%#x\n",
                                                       qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2, qmsg.time, qmsg.ptl.x, qmsg.ptl.y, qmsg.reserved);
                                WinDispatchMsg(g_habWorker, &qmsg);
                            }
                            VBoxServiceVerbose(2, "clipboard: Exited PM message loop. *pfShutdown=%RTbool\n", *pfShutdown);

                            RTThreadWait(g_ThreadListener, 60*1000, NULL);
                        }
                        g_ThreadListener = NIL_RTTHREAD;
                    }

                    /*
                     * Got a WM_QUIT, clean up.
                     */
                    if (g_hwndWorker != NULLHANDLE)
                    {
                        WinDestroyWindow(g_hwndWorker);
                        g_hwndWorker = NULLHANDLE;
                    }
                }
                else
                    VBoxServiceError("WinCreateWindow() failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
                /* no class deregistration in PM.  */
            }
            else
                VBoxServiceError("WinRegisterClass() failed, lasterr=%lx\n", WinGetLastError(g_habWorker));

            if (g_hmqCtrl != g_hmqWorker)
                WinDestroyMsgQueue(g_hmqWorker);
            g_hmqWorker = NULLHANDLE;
        }
        else
            VBoxServiceError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));

        if (g_habCtrl != g_habWorker)
            WinTerminate(g_habWorker);
        g_habWorker = NULLHANDLE;
    }
    else
        VBoxServiceError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE));

    return rc;
}


/** @copydoc VBOXSERVICE::pfnStop */
static DECLCALLBACK(void) VBoxServiceClipboardOS2Stop(void)
{
    if (    g_hmqWorker != NULLHANDLE
        &&  !WinPostQueueMsg(g_hmqWorker, WM_QUIT, NULL, NULL))
        VBoxServiceError("WinPostQueueMsg(g_hmqWorker, WM_QUIT, 0,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl));

    /* Must disconnect the clipboard here otherwise the listner won't quit and
       the service shutdown will not stop. */
    if (g_u32ClientId != 0)
    {
        if (g_hmqWorker != NULLHANDLE)
            RTThreadSleep(32);      /* fudge */

        VBoxServiceVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId);
        int rc = VbglR3ClipboardDisconnect(g_u32ClientId);
        if (RT_SUCCESS(rc))
            g_u32ClientId = 0;
        else
            VBoxServiceError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc);
    }
}


/** @copydoc VBOXSERVICE::pfnTerm */
static DECLCALLBACK(void) VBoxServiceClipboardOS2Term(void)
{
    if (g_u32ClientId != 0)
    {
        VBoxServiceVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId);
        int rc = VbglR3ClipboardDisconnect(g_u32ClientId);
        if (RT_SUCCESS(rc))
            g_u32ClientId = 0;
        else
            VBoxServiceError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc);
    }
    WinDestroyMsgQueue(g_hmqCtrl);
    g_hmqCtrl = NULLHANDLE;
    WinTerminate(g_habCtrl);
    g_habCtrl = NULLHANDLE;
}


/**
 * The OS/2 'clipboard' service description.
 */
VBOXSERVICE g_Clipboard =
{
    /* pszName. */
    "clipboard",
    /* pszDescription. */
    "Shared Clipboard",
    /* pszUsage. */
    ""
    ,
    /* pszOptions. */
    ""
    ,
    /* methods */
    VBoxServiceClipboardOS2PreInit,
    VBoxServiceClipboardOS2Option,
    VBoxServiceClipboardOS2Init,
    VBoxServiceClipboardOS2Worker,
    VBoxServiceClipboardOS2Stop,
    VBoxServiceClipboardOS2Term
};

