diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp | 1140 |
1 files changed, 1140 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp new file mode 100644 index 00000000..35b123e8 --- /dev/null +++ b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp @@ -0,0 +1,1140 @@ +/** $Id: VBoxServiceClipboard-os2.cpp $ */ +/** @file + * VBoxService - Guest Additions Clipboard Service, OS/2. + */ + +/* + * Copyright (C) 2007-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>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_vgsvc_clipboard VBoxService - Clipboard (OS/2) + * + * The Clipboard subservice provides clipboard sharing for OS/2 guests only. + * + * This was the second subservice that was added to VBoxService. OS/2 is a + * single user system and we don't provide any VBoxTray or VBoxClient like + * processes. Because it's kind of simple system, it became natural to put the + * clipboard sharing here in VBoxService for OS/2. + * + * In addition to integrating with the native OS/2 PM clipboard formats, we also + * try provide the Odin32, a windows API layer for OS/2 (developed by Sander van + * Leeuwen and friends, later mainly InnoTek), with additional formats. + * + * Bitmaps are currently not supported, but that can easily be added should the + * need ever arrise. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_BASE +#define INCL_PM +#define INCL_ERRORS +#include <os2.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/param.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/utf16.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/GuestHost/SharedClipboard.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" + + + + +/** + * @interface_method_impl{VBOXSERVICE,pfnPreInit} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2PreInit(void) +{ + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnOption} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Option(const char **ppszShort, int argc, char **argv, int *pi) +{ + NOREF(ppszShort); + NOREF(argc); + NOREF(argv); + NOREF(pi); + + return -1; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Init(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) + { + VGSvcError("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. + */ + VGSvcVerbose(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) + VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n", + lLastError, WinGetLastError(g_habCtrl)); + + VGSvcVerbose(2, "g_u32ClientId=%RX32 g_atomNothingChanged=%#x g_atomOdin32UnicodeText=%#x\n", + g_u32ClientId, g_atomNothingChanged, g_atomOdin32UnicodeText); + return VINF_SUCCESS; + } + + VGSvcError("Failed to connect to the clipboard service, rc=%Rrc!\n", rc); + } + else + VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n", + lLastError, WinGetLastError(g_habCtrl)); + } + else + VGSvcError("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 vgsvcClipboardOs2PollViewer(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) + VGSvcVerbose(3, "clipboard: viewer\n"); + else + VGSvcVerbose(3, "clipboard: poller\n"); + } +} + + +/** + * Advertise the formats available from the host. + * + * @param fFormats The formats available on the host. + */ +static void vgsvcClipboardOs2AdvertiseHostFormats(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_SHCL_FMT_UNICODETEXT/* | VBOX_SHCL_FMT_HTML ?? */)) + { + if (!WinSetClipbrdData(g_habWorker, 0, CF_TEXT, CFI_POINTER)) + VGSvcError("WinSetClipbrdData(,,CF_TEXT,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + if ( g_atomOdin32UnicodeText + && !WinSetClipbrdData(g_habWorker, 0, g_atomOdin32UnicodeText, CFI_POINTER)) + VGSvcError("WinSetClipbrdData(,,g_atomOdin32UnicodeText,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + } + if (fFormats & VBOX_SHCL_FMT_BITMAP) + { + /** @todo bitmaps */ + } + } + else + { + VGSvcError("WinSetClipbrdOwner failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + g_enmState = kClipboardState_Polling; + } + } + else + { + VGSvcError("WinEmptyClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + g_enmState = kClipboardState_Polling; + } + + if (g_enmState == kClipboardState_Polling) + { + g_fEmptyClipboard = true; + vgsvcClipboardOs2PollViewer(); + } + + WinCloseClipbrd(g_habWorker); + } + else + VGSvcError("vgsvcClipboardOs2AdvertiseHostFormats: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); +} + + +/** + * Converts (render) to an Odin32 clipboard format. + * + * We ASSUME we get windows data from the host and all we've got to do here is + * slapping an Odin32 header on it. + * + * @returns Pointer to the data (DosFreeMem). + * @param fFormat The host format. + * @param usFmt The PM/Odin32 format. + * @param pv The data in host formatting. + * @param cb The size of the data. + */ +static void *vgsvcClipboardOs2ConvertToOdin32(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 + { + VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), rc); + pvPM = NULL; + } + return pvPM; +} + + +/** + * Converts (render) to a PM clipboard format. + * + * @returns Pointer to the data (DosFreeMem). + * @param fFormat The host format. + * @param usFmt The PM/Odin32 format. + * @param pv The data in host formatting. + * @param cb The size of the data. + */ +static void *vgsvcClipboardOs2ConvertToPM(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 = vgsvcClipboardOs2ConvertToOdin32(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_SHCL_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 + { + VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), orc); + pvPM = NULL; + } + RTStrFree(pszLocale); + } + else + VGSvcError("RTStrUtf8ToCurrentCP() -> %Rrc\n", rc); + RTStrFree(pszUtf8); + } + else + VGSvcError("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 vgsvcClipboardOs2RenderFormat(USHORT usFmt) +{ + bool fSucceeded = false; + + /* + * Determine which format. + */ + uint32_t fFormat; + if ( usFmt == CF_TEXT + || usFmt == g_atomOdin32UnicodeText) + fFormat = VBOX_SHCL_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) + { + VGSvcVerbose(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)) + { + VGSvcVerbose(4, "clipboard: read %u bytes\n", cb); + + /* + * Convert the host clipboard data to PM clipboard data and set it. + */ + PVOID pvPM = vgsvcClipboardOs2ConvertToPM(fFormat, usFmt, pv, cb); + if (pvPM) + { + if (WinSetClipbrdData(g_habWorker, (ULONG)pvPM, usFmt, CFI_POINTER)) + fSucceeded = true; + else + { + VGSvcError("vgsvcClipboardOs2RenderFormat: WinSetClipbrdData(,%p,%#x, CF_POINTER) failed, lasterror=%lx\n", + pvPM, usFmt, WinGetLastError(g_habWorker)); + DosFreeMem(pvPM); + } + } + RTMemPageFree(pv, cbAllocated); + } + else + VGSvcError("vgsvcClipboardOs2RenderFormat: 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; + vgsvcClipboardOs2PollViewer(); + } +} + + +/** + * Sends data to the host. + * + * @param fFormat The data format the host is requesting. + */ +static void vgsvcClipboardOs2SendDataToHost(uint32_t fFormat) +{ + if (WinOpenClipbrd(g_habWorker)) + { + PRTUTF16 pwszFree = NULL; + void *pv = NULL; + uint32_t cb = 0; + + if (fFormat & VBOX_SHCL_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) + VGSvcError("vgsvcClipboardOs2SendDataToHost: couldn't find data for %#x\n", fFormat); + + /* + * Now, sent whatever we've got to the host (it's waiting). + */ + VGSvcVerbose(4, "clipboard: writing %pv/%#d (fFormat=%#x)\n", pv, cb, fFormat); + VbglR3ClipboardWriteData(g_u32ClientId, fFormat, pv, cb); + RTUtf16Free(pwszFree); + + WinCloseClipbrd(g_habWorker); + } + else + { + VGSvcError("vgsvcClipboardOs2SendDataToHost: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + VGSvcVerbose(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 vgsvcClipboardOs2ReportFormats(void) +{ + uint32_t fFormats = 0; + ULONG ulFormat = 0; + while ((ulFormat = WinEnumClipbrdFmts(g_habWorker, ulFormat)) != 0) + { + if ( ulFormat == CF_TEXT + || ulFormat == g_atomOdin32UnicodeText) + fFormats |= VBOX_SHCL_FMT_UNICODETEXT; + /** @todo else bitmaps and stuff. */ + } + VGSvcVerbose(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 vgsvcClipboardOs2Poll(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; + vgsvcClipboardOs2ReportFormats(); + + /* 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)) + VGSvcVerbose(4, "clipboard: Added dummy item.\n"); + else + { + VGSvcError("vgsvcClipboardOs2Poll: WinSetClipbrdData failed, lasterr=%#lx\n", WinGetLastError(g_habWorker)); + DosFreeMem(pv); + } + } + else + VGSvcError("vgsvcClipboardOs2Poll: DosAllocSharedMem(,,1,) -> %ld\n", rc); + } + else if (!g_fEmptyClipboard) + { + g_fEmptyClipboard = true; + VGSvcVerbose(3, "Reporting empty clipboard\n"); + VbglR3ClipboardReportFormats(g_u32ClientId, 0); + } + } + WinCloseClipbrd(g_habWorker); + } + else + VGSvcError("vgsvcClipboardOs2Poll: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); +} + + +/** + * The clipboard we owned was destroyed by someone else. + */ +static void vgsvcClipboardOs2Destroyed(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; + VGSvcVerbose(3, "Reporting empty clipboard\n"); + VbglR3ClipboardReportFormats(g_u32ClientId, 0); + + vgsvcClipboardOs2PollViewer(); +} + + +/** + * 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 vgsvcClipboardOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) +{ + if (msg != WM_TIMER) + VGSvcVerbose(6, "vgsvcClipboardOs2WinProc: 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)); + vgsvcClipboardOs2Poll(); + 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)); + vgsvcClipboardOs2Destroyed(); + 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)); + vgsvcClipboardOs2RenderFormat(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_SHCL_HOST_MSG_FORMATS_REPORT: + vgsvcClipboardOs2AdvertiseHostFormats(LONGFROMMP(mp1)); + break; + + /* + * Listener message - the host wish to read our clipboard data. + */ + case WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA: + vgsvcClipboardOs2SendDataToHost(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 */ + vgsvcClipboardOs2Poll(); + vgsvcClipboardOs2PollViewer(); + 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) vgsvcClipboardOs2Listener(RTTHREAD ThreadSelf, void *pvUser) +{ + bool volatile *pfShutdown = (bool volatile *)pvUser; + int rc = VERR_GENERAL_FAILURE; + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: 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); + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Started successfully\n"); + + /* + * Loop until termination is requested. + */ + bool fQuit = false; + while (!*pfShutdown && !fQuit) + { + uint32_t Msg; + uint32_t fFormats; + rc = VbglR3ClipboardGetHostMsgOld(g_u32ClientId, &Msg, &fFormats); + if (RT_SUCCESS(rc)) + { + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: 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_SHCL_HOST_MSG_FORMATS_REPORT: + if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT, + MPFROMLONG(fFormats), 0)) + VGSvcError("WinPostMsg(%lx, FORMATS,,) failed, lasterr=%#lx\n", + g_hwndWorker, WinGetLastError(g_habListener)); + break; + + /* + * The host needs data in the specified format. + */ + case VBOX_SHCL_HOST_MSG_READ_DATA: + if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA, + MPFROMLONG(fFormats), 0)) + VGSvcError("WinPostMsg(%lx, READ_DATA,,) failed, lasterr=%#lx\n", + g_hwndWorker, WinGetLastError(g_habListener)); + break; + + /* + * The host is terminating. + */ + case VBOX_SHCL_HOST_MSG_QUIT: + fQuit = true; + break; + + default: + VGSvcVerbose(1, "vgsvcClipboardOs2Listener: Unknown message %RU32\n", Msg); + break; + } + } + else + { + if (*pfShutdown) + break; + VGSvcError("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); + VGSvcVerbose(3, "vgsvcClipboardOs2Listener: terminating, rc=%Rrc\n", rc); + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vgsvcClipboardOs2Worker(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", vgsvcClipboardOs2WinProc, 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) + { + VGSvcVerbose(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, vgsvcClipboardOs2Listener, (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. + */ + VGSvcVerbose(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) + VGSvcVerbose(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); + } + VGSvcVerbose(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 + VGSvcError("WinCreateWindow() failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + /* no class deregistration in PM. */ + } + else + VGSvcError("WinRegisterClass() failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + + if (g_hmqCtrl != g_hmqWorker) + WinDestroyMsgQueue(g_hmqWorker); + g_hmqWorker = NULLHANDLE; + } + else + VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habWorker)); + + if (g_habCtrl != g_habWorker) + WinTerminate(g_habWorker); + g_habWorker = NULLHANDLE; + } + else + VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE)); + + return rc; +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vgsvcClipboardOs2Stop(void) +{ + if ( g_hmqWorker != NULLHANDLE + && !WinPostQueueMsg(g_hmqWorker, WM_QUIT, NULL, NULL)) + VGSvcError("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 */ + + VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId); + int rc = VbglR3ClipboardDisconnect(g_u32ClientId); + if (RT_SUCCESS(rc)) + g_u32ClientId = 0; + else + VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc); + } +} + + +/** + * @interface_method_impl{VBOXSERVICE,pfnTerm} + */ +static DECLCALLBACK(void) vgsvcClipboardOs2Term(void) +{ + if (g_u32ClientId != 0) + { + VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId); + int rc = VbglR3ClipboardDisconnect(g_u32ClientId); + if (RT_SUCCESS(rc)) + g_u32ClientId = 0; + else + VGSvcError("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 */ + vgsvcClipboardOs2PreInit, + vgsvcClipboardOs2Option, + vgsvcClipboardOs2Init, + vgsvcClipboardOs2Worker, + vgsvcClipboardOs2Stop, + vgsvcClipboardOs2Term +}; + |