VBoxServiceClipboard-os2.cpp revision b4662837bd3a53da792c1fd34a2f0e623aa3d9e2
/** $Id$ */
/** @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;
* 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/semaphore.h>
#include <VBox/VBoxGuestLib.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. */
/** The HAB of the control thread (main). */
/** The HMQ of the control thread (main). */
/** The Listener thread handle. */
/** The HAB of the listener thread. */
/** The HMQ of the listener thread. */
/** Indicator that gets set if the listener thread is successfully initialized. */
static bool volatile g_fListenerOkay = false;
/** The HAB of the worker thread. */
/** The HMQ of the worker thread. */
/** The object window handle. */
/** The timer id returned by WinStartTimer. */
/** 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. */
/** WinSetClipbrdViewer call in progress, ignore WM_DRAWCLIPBOARD. */
/** We're monitoring the clipboard as a viewer. */
/** We're monitoring the clipboard using polling.
* This usually means something is wrong... */
/** We're destroying the clipboard content, ignore WM_DESTROYCLIPBOARD. */
/** We're owning the clipboard (i.e. we have data on it). */
/** 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. */
/** @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)
{
return -1;
}
/** @copydoc VBOXSERVICE::pfnInit */
static DECLCALLBACK(int) VBoxServiceClipboardOS2Init(void)
{
int rc = VERR_GENERAL_FAILURE;
g_ThreadCtrl = RTThreadSelf();
/*
* Make PM happy.
*/
/*
* 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)
{
return VERR_GENERAL_FAILURE;
}
if (g_hmqCtrl != NULLHANDLE)
{
/*
* Create the 'nothing-changed' format.
*/
if (g_atomNothingChanged == 0)
g_atomNothingChanged = WinFindAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
if (g_atomNothingChanged)
{
/*
* Connect to the clipboard service.
*/
if (RT_SUCCESS(rc))
{
/*
* Create any extra clipboard type atoms, like the odin unicode text.
*/
if (g_atomOdin32UnicodeText == 0)
if (g_atomOdin32UnicodeText == 0)
VBoxServiceError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
return VINF_SUCCESS;
}
}
else
VBoxServiceError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
}
else
return rc;
}
/**
* Check that we're still the view / try make us the viewer.
*/
static void VBoxServiceClipboardOS2PollViewer(void)
{
const int iOrgState = g_enmState;
if (hwndClipboardViewer == g_hwndWorker)
return;
if (hwndClipboardViewer == NULLHANDLE)
{
/* The API will send a WM_DRAWCLIPBOARD message before returning. */
else
}
else
if ((int)g_enmState != iOrgState)
{
if (g_enmState == kClipboardState_Viewer)
else
}
}
/**
* Advertise the formats available from the host.
*/
{
/*
* 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)
if (g_enmState == kClipboardState_Owner)
if (WinEmptyClipbrd(g_habWorker))
{
/*
* Take clipboard ownership.
*/
{
/*
* Do the format advertising.
*/
{
VBoxServiceError("WinSetClipbrdData(,,CF_TEXT,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
VBoxServiceError("WinSetClipbrdData(,,g_atomOdin32UnicodeText,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
}
{
/** @todo bitmaps */
}
}
else
{
}
}
else
{
}
if (g_enmState == kClipboardState_Polling)
{
g_fEmptyClipboard = true;
}
}
else
VBoxServiceError("VBoxServiceClipboardOS2AdvertiseHostFormats: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
}
static void *VBoxServiceClipboardOs2ConvertToOdin32(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
{
APIRET rc = DosAllocSharedMem(&pvPM, NULL, cb + sizeof(CLIPHEADER), OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
if (rc)
{
if (usFmt == g_atomOdin32UnicodeText)
else
AssertFailed();
}
else
{
}
return pvPM;
}
static void *VBoxServiceClipboardOs2ConvertToPM(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
{
/*
* 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 == ...*/
)
)
{
/*
* 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
*/
char *pszUtf8;
if (RT_SUCCESS(rc))
{
char *pszLocale;
if (RT_SUCCESS(rc))
{
APIRET orc = DosAllocSharedMem(&pvPM, NULL, cbPM, OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
else
{
}
}
else
}
else
}
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.
*
*/
{
bool fSucceeded = false;
/*
* Determine which format.
*/
|| usFmt == g_atomOdin32UnicodeText)
else /** @todo bitmaps */
fFormat = 0;
if (fFormat)
{
/*
* Query the data from the host.
* This might require two iterations because of buffer guessing.
*/
int rc = VERR_NO_MEMORY;
if (pv)
{
if (rc == VINF_BUFFER_OVERFLOW)
{
}
if (RT_FAILURE(rc))
}
if (RT_SUCCESS(rc))
{
/*
* Convert the host clipboard data to PM clipboard data and set it.
*/
if (pvPM)
{
fSucceeded = true;
else
{
VBoxServiceError("VBoxServiceClipboardOS2RenderFormat: WinSetClipbrdData(,%p,%#x, CF_POINTER) failed, lasterror=%lx\n",
}
}
}
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)
{
g_fEmptyClipboard = true;
}
}
{
if (WinOpenClipbrd(g_habWorker))
{
{
/* Got any odin32 unicode text? */
if ( pHdr
{
}
/* Got any CF_TEXT? */
if ( !pv
{
char *pszUtf8;
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
}
}
}
}
if (!pv)
/*
* Now, sent whatever we've got to the host (it's waiting).
*/
}
else
{
VBoxServiceError("VBoxServiceClipboardOS2SendDataToHost: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
}
}
/**
* Figure out what's on the clipboard and report it to the host.
*/
static void VBoxServiceClipboardOS2ReportFormats(void)
{
{
|| ulFormat == g_atomOdin32UnicodeText)
/** @todo else bitmaps and stuff. */
}
}
/**
* 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.
*/
{
if (WinEnumClipbrdFmts(g_habWorker, 0) != 0)
{
g_fEmptyClipboard = false;
/* inject the dummy */
APIRET rc = DosAllocSharedMem(&pv, NULL, 1, OBJ_GIVEABLE | OBJ_GETTABLE | PAG_READ | PAG_WRITE | PAG_COMMIT);
{
else
{
VBoxServiceError("VBoxServiceClipboardOS2Poll: WinSetClipbrdData failed, lasterr=%#lx\n", WinGetLastError(g_habWorker));
DosFreeMem(pv);
}
}
else
}
else if (!g_fEmptyClipboard)
{
g_fEmptyClipboard = true;
}
}
}
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. */
/* switch to polling state and notify the host. */
g_fEmptyClipboard = true;
}
/**
* 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)
{
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.
*
* using for the viewer checks and polling fallback.
*/
case WM_CREATE:
g_fEmptyClipboard = true;
return NULL; /* FALSE(/NULL) == Continue*/
case WM_DESTROY:
g_idWorkerTimer = ~0UL;
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;
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. */
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.
*
* 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:
g_fEmptyClipboard = true;
break;
/*
* Listener message - the host has new formats to offer.
*/
break;
/*
* Listener message - the host wish to read our clipboard data.
*/
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
break;
/* Lost the position as clipboard viewer?*/
if (g_enmState == kClipboardState_Viewer)
{
break;
}
/* poll for changes */
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:
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:
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.
*/
{
bool volatile *pfShutdown = (bool volatile *)pvUser;
int rc = VERR_GENERAL_FAILURE;
g_habListener = WinInitialize(0);
if (g_habListener != NULLHANDLE)
{
if (g_hmqListener != NULLHANDLE)
{
/*
* Tell the worker thread that we're good.
*/
rc = VINF_SUCCESS;
ASMAtomicXchgBool(&g_fListenerOkay, true);
/*
* Loop until termination is requested.
*/
bool fQuit = false;
while (!*pfShutdown && !fQuit)
{
if (RT_SUCCESS(rc))
{
switch (Msg)
{
/*
* The host has announced available clipboard formats.
* Forward the information to the window, so it can later
* respond do WM_RENDERFORMAT message.
*/
MPFROMLONG(fFormats), 0))
VBoxServiceError("WinPostMsg(%lx, FORMATS,,) failed, lasterr=%#lx\n",
break;
/*
* The host needs data in the specified format.
*/
MPFROMLONG(fFormats), 0))
VBoxServiceError("WinPostMsg(%lx, READ_DATA,,) failed, lasterr=%#lx\n",
break;
/*
* The host is terminating.
*/
fQuit = true;
break;
default:
break;
}
}
else
{
if (*pfShutdown)
break;
RTThreadSleep(1000);
}
} /* the loop */
}
}
/* Signal our semaphore to make the worker catch on. */
return rc;
}
/** @copydoc VBOXSERVICE::pfnWorker */
{
int rc = VERR_GENERAL_FAILURE;
/*
* Standard PM init.
*/
if (g_habWorker != NULLHANDLE)
{
if (g_hmqWorker != NULLHANDLE)
{
if (g_hmqWorker != g_hmqCtrl)
/*
* Create the object window.
*/
if (WinRegisterClass(g_habWorker, (PCSZ)"VBoxServiceClipboardClass", VBoxServiceClipboardOS2WinProc, 0, 0))
{
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;
if (RT_SUCCESS(rc))
{
if (!g_fListenerOkay)
if (g_fListenerOkay)
{
/*
* Tell the control thread that it can continue
* spawning services.
*/
/*
* The PM event pump.
*/
rc = VINF_SUCCESS;
{
}
}
}
/*
* Got a WM_QUIT, clean up.
*/
if (g_hwndWorker != NULLHANDLE)
{
}
}
else
/* no class deregistration in PM. */
}
else
if (g_hmqCtrl != g_hmqWorker)
}
else
if (g_habCtrl != g_habWorker)
}
else
return rc;
}
/** @copydoc VBOXSERVICE::pfnStop */
static DECLCALLBACK(void) VBoxServiceClipboardOS2Stop(void)
{
if ( g_hmqWorker != NULLHANDLE
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)
if (RT_SUCCESS(rc))
g_u32ClientId = 0;
else
}
}
/** @copydoc VBOXSERVICE::pfnTerm */
static DECLCALLBACK(void) VBoxServiceClipboardOS2Term(void)
{
if (g_u32ClientId != 0)
{
if (RT_SUCCESS(rc))
g_u32ClientId = 0;
else
}
}
/**
* The OS/2 'clipboard' service description.
*/
{
/* pszName. */
"clipboard",
/* pszDescription. */
"Shared Clipboard",
/* pszUsage. */
""
,
/* pszOptions. */
""
,
/* methods */
};