/*
 * Clipboard unit tests
 *
 * Copyright 2006 Kevin Koltzau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS
#define NONAMELESSUNION

#include <stdarg.h>
#include <stdio.h>

#include "windef.h"
#include "winbase.h"
#include "objbase.h"

#include "wine/test.h"

#define InitFormatEtc(fe, cf, med) \
        {\
        (fe).cfFormat=cf;\
        (fe).dwAspect=DVASPECT_CONTENT;\
        (fe).ptd=NULL;\
        (fe).tymed=med;\
        (fe).lindex=-1;\
        };

static inline char *dump_fmtetc(FORMATETC *fmt)
{
    static char buf[100];

    snprintf(buf, sizeof(buf), "cf %04x ptd %p aspect %x lindex %d tymed %x",
             fmt->cfFormat, fmt->ptd, fmt->dwAspect, fmt->lindex, fmt->tymed);
    return buf;
}

typedef struct DataObjectImpl {
    const IDataObjectVtbl *lpVtbl;
    LONG ref;

    FORMATETC *fmtetc;
    UINT fmtetc_cnt;

    HANDLE text;
    IStream *stm;
    IStorage *stg;
} DataObjectImpl;

typedef struct EnumFormatImpl {
    const IEnumFORMATETCVtbl *lpVtbl;
    LONG ref;

    FORMATETC *fmtetc;
    UINT fmtetc_cnt;

    UINT cur;
} EnumFormatImpl;

static BOOL expect_DataObjectImpl_QueryGetData = TRUE;
static ULONG DataObjectImpl_GetData_calls = 0;
static ULONG DataObjectImpl_GetDataHere_calls = 0;
static ULONG DataObjectImpl_EnumFormatEtc_calls = 0;

static UINT cf_stream, cf_storage, cf_global, cf_another, cf_onemore;

static HRESULT EnumFormatImpl_Create(FORMATETC *fmtetc, UINT size, LPENUMFORMATETC *lplpformatetc);

static HRESULT WINAPI EnumFormatImpl_QueryInterface(IEnumFORMATETC *iface, REFIID riid, LPVOID *ppvObj)
{
    EnumFormatImpl *This = (EnumFormatImpl*)iface;

    if (IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_IEnumFORMATETC)) {
        IEnumFORMATETC_AddRef(iface);
        *ppvObj = This;
        return S_OK;
    }
    *ppvObj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI EnumFormatImpl_AddRef(IEnumFORMATETC *iface)
{
    EnumFormatImpl *This = (EnumFormatImpl*)iface;
    LONG ref = InterlockedIncrement(&This->ref);
    return ref;
}

static ULONG WINAPI EnumFormatImpl_Release(IEnumFORMATETC *iface)
{
    EnumFormatImpl *This = (EnumFormatImpl*)iface;
    ULONG ref = InterlockedDecrement(&This->ref);

    if(!ref) {
        HeapFree(GetProcessHeap(), 0, This->fmtetc);
        HeapFree(GetProcessHeap(), 0, This);
    }

    return ref;
}

static HRESULT WINAPI EnumFormatImpl_Next(IEnumFORMATETC *iface, ULONG celt,
                                          FORMATETC *rgelt, ULONG *pceltFetched)
{
    EnumFormatImpl *This = (EnumFormatImpl*)iface;
    ULONG count, i;

    trace("next: count %d cur %d\n", celt, This->cur);

    if(!rgelt)
        return E_INVALIDARG;

    count = min(celt, This->fmtetc_cnt - This->cur);
    for(i = 0; i < count; i++, This->cur++, rgelt++)
    {
        *rgelt = This->fmtetc[This->cur];
        if(rgelt->ptd)
        {
            DWORD size = This->fmtetc[This->cur].ptd->tdSize;
            rgelt->ptd = CoTaskMemAlloc(size);
            memcpy(rgelt->ptd, This->fmtetc[This->cur].ptd, size);
        }
    }
    if(pceltFetched)
        *pceltFetched = count;
    return count == celt ? S_OK : S_FALSE;
}

static HRESULT WINAPI EnumFormatImpl_Skip(IEnumFORMATETC *iface, ULONG celt)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI EnumFormatImpl_Reset(IEnumFORMATETC *iface)
{
    EnumFormatImpl *This = (EnumFormatImpl*)iface;

    This->cur = 0;
    return S_OK;
}

static HRESULT WINAPI EnumFormatImpl_Clone(IEnumFORMATETC *iface, IEnumFORMATETC **ppenum)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static const IEnumFORMATETCVtbl VT_EnumFormatImpl = {
    EnumFormatImpl_QueryInterface,
    EnumFormatImpl_AddRef,
    EnumFormatImpl_Release,
    EnumFormatImpl_Next,
    EnumFormatImpl_Skip,
    EnumFormatImpl_Reset,
    EnumFormatImpl_Clone
};

static HRESULT EnumFormatImpl_Create(FORMATETC *fmtetc, UINT fmtetc_cnt, IEnumFORMATETC **lplpformatetc)
{
    EnumFormatImpl *ret;

    ret = HeapAlloc(GetProcessHeap(), 0, sizeof(EnumFormatImpl));
    ret->lpVtbl = &VT_EnumFormatImpl;
    ret->ref = 1;
    ret->cur = 0;
    ret->fmtetc_cnt = fmtetc_cnt;
    ret->fmtetc = HeapAlloc(GetProcessHeap(), 0, fmtetc_cnt*sizeof(FORMATETC));
    memcpy(ret->fmtetc, fmtetc, fmtetc_cnt*sizeof(FORMATETC));
    *lplpformatetc = (LPENUMFORMATETC)ret;
    return S_OK;
}

static HRESULT WINAPI DataObjectImpl_QueryInterface(IDataObject *iface, REFIID riid, LPVOID *ppvObj)
{
    DataObjectImpl *This = (DataObjectImpl*)iface;

    if (IsEqualGUID(riid, &IID_IUnknown) || IsEqualGUID(riid, &IID_IDataObject)) {
        IDataObject_AddRef(iface);
        *ppvObj = This;
        return S_OK;
    }
    *ppvObj = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI DataObjectImpl_AddRef(IDataObject* iface)
{
    DataObjectImpl *This = (DataObjectImpl*)iface;
    ULONG ref = InterlockedIncrement(&This->ref);
    return ref;
}

static ULONG WINAPI DataObjectImpl_Release(IDataObject* iface)
{
    DataObjectImpl *This = (DataObjectImpl*)iface;
    ULONG ref = InterlockedDecrement(&This->ref);

    if(!ref)
    {
        int i;
        if(This->text) GlobalFree(This->text);
        for(i = 0; i < This->fmtetc_cnt; i++)
            HeapFree(GetProcessHeap(), 0, This->fmtetc[i].ptd);
        HeapFree(GetProcessHeap(), 0, This->fmtetc);
        if(This->stm) IStream_Release(This->stm);
        if(This->stg) IStorage_Release(This->stg);
        HeapFree(GetProcessHeap(), 0, This);
    }

    return ref;
}

static HRESULT WINAPI DataObjectImpl_GetData(IDataObject* iface, FORMATETC *pformatetc, STGMEDIUM *pmedium)
{
    DataObjectImpl *This = (DataObjectImpl*)iface;
    UINT i;
    BOOL foundFormat = FALSE;

    trace("getdata: %s\n", dump_fmtetc(pformatetc));

    DataObjectImpl_GetData_calls++;

    if(pformatetc->lindex != -1)
        return DV_E_FORMATETC;

    for(i = 0; i < This->fmtetc_cnt; i++)
    {
        if(This->fmtetc[i].cfFormat == pformatetc->cfFormat)
        {
            foundFormat = TRUE;
            if(This->fmtetc[i].tymed & pformatetc->tymed)
            {
                pmedium->pUnkForRelease = (LPUNKNOWN)iface;
                IUnknown_AddRef(pmedium->pUnkForRelease);

                if(pformatetc->cfFormat == CF_TEXT || pformatetc->cfFormat == cf_global)
                {
                    pmedium->tymed = TYMED_HGLOBAL;
                    U(*pmedium).hGlobal = This->text;
                }
                else if(pformatetc->cfFormat == cf_stream)
                {
                    pmedium->tymed = TYMED_ISTREAM;
                    IStream_AddRef(This->stm);
                    U(*pmedium).pstm = This->stm;
                }
                else if(pformatetc->cfFormat == cf_storage || pformatetc->cfFormat == cf_another)
                {
                    pmedium->tymed = TYMED_ISTORAGE;
                    IStorage_AddRef(This->stg);
                    U(*pmedium).pstg = This->stg;
                }
                return S_OK;
            }
        }
    }

    return foundFormat ? DV_E_TYMED : DV_E_FORMATETC;
}

static HRESULT WINAPI DataObjectImpl_GetDataHere(IDataObject* iface, FORMATETC *pformatetc, STGMEDIUM *pmedium)
{
    trace("getdatahere: %s\n", dump_fmtetc(pformatetc));
    DataObjectImpl_GetDataHere_calls++;

    return E_NOTIMPL;
}

static HRESULT WINAPI DataObjectImpl_QueryGetData(IDataObject* iface, FORMATETC *pformatetc)
{
    DataObjectImpl *This = (DataObjectImpl*)iface;
    UINT i;
    BOOL foundFormat = FALSE;

    trace("querygetdata: %s\n", dump_fmtetc(pformatetc));
    if (!expect_DataObjectImpl_QueryGetData)
        ok(0, "unexpected call to DataObjectImpl_QueryGetData\n");

    if(pformatetc->lindex != -1)
        return DV_E_LINDEX;

    for(i=0; i<This->fmtetc_cnt; i++) {
        if(This->fmtetc[i].cfFormat == pformatetc->cfFormat) {
            foundFormat = TRUE;
            if(This->fmtetc[i].tymed == pformatetc->tymed)
                return S_OK;
        }
    }
    return foundFormat?DV_E_FORMATETC:DV_E_TYMED;
}

static HRESULT WINAPI DataObjectImpl_GetCanonicalFormatEtc(IDataObject* iface, FORMATETC *pformatectIn,
                                                           FORMATETC *pformatetcOut)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI DataObjectImpl_SetData(IDataObject* iface, FORMATETC *pformatetc,
                                             STGMEDIUM *pmedium, BOOL fRelease)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI DataObjectImpl_EnumFormatEtc(IDataObject* iface, DWORD dwDirection,
                                                   IEnumFORMATETC **ppenumFormatEtc)
{
    DataObjectImpl *This = (DataObjectImpl*)iface;

    DataObjectImpl_EnumFormatEtc_calls++;

    if(dwDirection != DATADIR_GET) {
        ok(0, "unexpected direction %d\n", dwDirection);
        return E_NOTIMPL;
    }
    return EnumFormatImpl_Create(This->fmtetc, This->fmtetc_cnt, ppenumFormatEtc);
}

static HRESULT WINAPI DataObjectImpl_DAdvise(IDataObject* iface, FORMATETC *pformatetc, DWORD advf,
                                             IAdviseSink *pAdvSink, DWORD *pdwConnection)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI DataObjectImpl_DUnadvise(IDataObject* iface, DWORD dwConnection)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI DataObjectImpl_EnumDAdvise(IDataObject* iface, IEnumSTATDATA **ppenumAdvise)
{
    ok(0, "unexpected call\n");
    return E_NOTIMPL;
}

static const IDataObjectVtbl VT_DataObjectImpl =
{
    DataObjectImpl_QueryInterface,
    DataObjectImpl_AddRef,
    DataObjectImpl_Release,
    DataObjectImpl_GetData,
    DataObjectImpl_GetDataHere,
    DataObjectImpl_QueryGetData,
    DataObjectImpl_GetCanonicalFormatEtc,
    DataObjectImpl_SetData,
    DataObjectImpl_EnumFormatEtc,
    DataObjectImpl_DAdvise,
    DataObjectImpl_DUnadvise,
    DataObjectImpl_EnumDAdvise
};

static HRESULT DataObjectImpl_CreateText(LPCSTR text, LPDATAOBJECT *lplpdataobj)
{
    DataObjectImpl *obj;

    obj = HeapAlloc(GetProcessHeap(), 0, sizeof(DataObjectImpl));
    obj->lpVtbl = &VT_DataObjectImpl;
    obj->ref = 1;
    obj->text = GlobalAlloc(GMEM_MOVEABLE, strlen(text) + 1);
    strcpy(GlobalLock(obj->text), text);
    GlobalUnlock(obj->text);
    obj->stm = NULL;
    obj->stg = NULL;

    obj->fmtetc_cnt = 1;
    obj->fmtetc = HeapAlloc(GetProcessHeap(), 0, obj->fmtetc_cnt*sizeof(FORMATETC));
    InitFormatEtc(obj->fmtetc[0], CF_TEXT, TYMED_HGLOBAL);

    *lplpdataobj = (LPDATAOBJECT)obj;
    return S_OK;
}

const char *cmpl_stm_data = "complex stream";
const char *cmpl_text_data = "complex text";
const WCHAR device_name[] = {'m','y','d','e','v',0};

static HRESULT DataObjectImpl_CreateComplex(LPDATAOBJECT *lplpdataobj)
{
    DataObjectImpl *obj;
    ILockBytes *lbs;
    DEVMODEW dm;

    obj = HeapAlloc(GetProcessHeap(), 0, sizeof(DataObjectImpl));
    obj->lpVtbl = &VT_DataObjectImpl;
    obj->ref = 1;
    obj->text = GlobalAlloc(GMEM_MOVEABLE, strlen(cmpl_text_data) + 1);
    strcpy(GlobalLock(obj->text), cmpl_text_data);
    GlobalUnlock(obj->text);
    CreateStreamOnHGlobal(NULL, TRUE, &obj->stm);
    IStream_Write(obj->stm, cmpl_stm_data, strlen(cmpl_stm_data), NULL);

    CreateILockBytesOnHGlobal(NULL, TRUE, &lbs);
    StgCreateDocfileOnILockBytes(lbs, STGM_CREATE|STGM_SHARE_EXCLUSIVE|STGM_READWRITE, 0, &obj->stg);
    ILockBytes_Release(lbs);

    obj->fmtetc_cnt = 8;
    /* zeroing here since FORMATETC has a hole in it, and it's confusing to have this uninitialised. */
    obj->fmtetc = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, obj->fmtetc_cnt*sizeof(FORMATETC));
    InitFormatEtc(obj->fmtetc[0], CF_TEXT, TYMED_HGLOBAL);
    InitFormatEtc(obj->fmtetc[1], cf_stream, TYMED_ISTREAM);
    InitFormatEtc(obj->fmtetc[2], cf_storage, TYMED_ISTORAGE);
    InitFormatEtc(obj->fmtetc[3], cf_another, TYMED_ISTORAGE|TYMED_ISTREAM|TYMED_HGLOBAL);
    memset(&dm, 0, sizeof(dm));
    dm.dmSize = sizeof(dm);
    dm.dmDriverExtra = 0;
    lstrcpyW(dm.dmDeviceName, device_name);
    obj->fmtetc[3].ptd = HeapAlloc(GetProcessHeap(), 0, FIELD_OFFSET(DVTARGETDEVICE, tdData) + sizeof(device_name) + dm.dmSize + dm.dmDriverExtra);
    obj->fmtetc[3].ptd->tdSize = FIELD_OFFSET(DVTARGETDEVICE, tdData) + sizeof(device_name) + dm.dmSize + dm.dmDriverExtra;
    obj->fmtetc[3].ptd->tdDriverNameOffset = FIELD_OFFSET(DVTARGETDEVICE, tdData);
    obj->fmtetc[3].ptd->tdDeviceNameOffset = 0;
    obj->fmtetc[3].ptd->tdPortNameOffset   = 0;
    obj->fmtetc[3].ptd->tdExtDevmodeOffset = obj->fmtetc[3].ptd->tdDriverNameOffset + sizeof(device_name);
    lstrcpyW((WCHAR*)obj->fmtetc[3].ptd->tdData, device_name);
    memcpy(obj->fmtetc[3].ptd->tdData + sizeof(device_name), &dm, dm.dmSize + dm.dmDriverExtra);

    InitFormatEtc(obj->fmtetc[4], cf_global, TYMED_HGLOBAL);
    InitFormatEtc(obj->fmtetc[5], cf_another, TYMED_HGLOBAL);
    InitFormatEtc(obj->fmtetc[6], cf_another, 0xfffff);
    InitFormatEtc(obj->fmtetc[7], cf_another, 0xfffff);
    obj->fmtetc[7].dwAspect = DVASPECT_ICON;

    *lplpdataobj = (LPDATAOBJECT)obj;
    return S_OK;
}

static void test_get_clipboard(void)
{
    HRESULT hr;
    IDataObject *data_obj;
    FORMATETC fmtetc;
    STGMEDIUM stgmedium;

    hr = OleGetClipboard(NULL);
    ok(hr == E_INVALIDARG, "OleGetClipboard(NULL) should return E_INVALIDARG instead of 0x%08x\n", hr);

    hr = OleGetClipboard(&data_obj);
    ok(hr == S_OK, "OleGetClipboard failed with error 0x%08x\n", hr);

    /* test IDataObject_QueryGetData */

    /* clipboard's IDataObject_QueryGetData shouldn't defer to our IDataObject_QueryGetData */
    expect_DataObjectImpl_QueryGetData = FALSE;

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    hr = IDataObject_QueryGetData(data_obj, &fmtetc);
    ok(hr == S_OK, "IDataObject_QueryGetData failed with error 0x%08x\n", hr);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.dwAspect = 0xdeadbeef;
    hr = IDataObject_QueryGetData(data_obj, &fmtetc);
    ok(hr == DV_E_FORMATETC, "IDataObject_QueryGetData should have failed with DV_E_FORMATETC instead of 0x%08x\n", hr);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.dwAspect = DVASPECT_THUMBNAIL;
    hr = IDataObject_QueryGetData(data_obj, &fmtetc);
    ok(hr == DV_E_FORMATETC, "IDataObject_QueryGetData should have failed with DV_E_FORMATETC instead of 0x%08x\n", hr);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.lindex = 256;
    hr = IDataObject_QueryGetData(data_obj, &fmtetc);
    ok(hr == DV_E_FORMATETC || broken(hr == S_OK),
        "IDataObject_QueryGetData should have failed with DV_E_FORMATETC instead of 0x%08x\n", hr);
    if (hr == S_OK)
        ReleaseStgMedium(&stgmedium);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.cfFormat = CF_RIFF;
    hr = IDataObject_QueryGetData(data_obj, &fmtetc);
    ok(hr == DV_E_CLIPFORMAT, "IDataObject_QueryGetData should have failed with DV_E_CLIPFORMAT instead of 0x%08x\n", hr);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.tymed = TYMED_FILE;
    hr = IDataObject_QueryGetData(data_obj, &fmtetc);
    ok(hr == S_OK, "IDataObject_QueryGetData failed with error 0x%08x\n", hr);

    expect_DataObjectImpl_QueryGetData = TRUE;

    /* test IDataObject_GetData */

    DataObjectImpl_GetData_calls = 0;

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    hr = IDataObject_GetData(data_obj, &fmtetc, &stgmedium);
    ok(hr == S_OK, "IDataObject_GetData failed with error 0x%08x\n", hr);
    ReleaseStgMedium(&stgmedium);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.dwAspect = 0xdeadbeef;
    hr = IDataObject_GetData(data_obj, &fmtetc, &stgmedium);
    ok(hr == S_OK, "IDataObject_GetData failed with error 0x%08x\n", hr);
    ReleaseStgMedium(&stgmedium);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.dwAspect = DVASPECT_THUMBNAIL;
    hr = IDataObject_GetData(data_obj, &fmtetc, &stgmedium);
    ok(hr == S_OK, "IDataObject_GetData failed with error 0x%08x\n", hr);
    ReleaseStgMedium(&stgmedium);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.lindex = 256;
    hr = IDataObject_GetData(data_obj, &fmtetc, &stgmedium);
    ok(hr == DV_E_FORMATETC || broken(hr == S_OK), "IDataObject_GetData should have failed with DV_E_FORMATETC instead of 0x%08x\n", hr);
    if (hr == S_OK)
    {
        /* undo the unexpected success */
        DataObjectImpl_GetData_calls--;
        ReleaseStgMedium(&stgmedium);
    }

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.cfFormat = CF_RIFF;
    hr = IDataObject_GetData(data_obj, &fmtetc, &stgmedium);
    ok(hr == DV_E_FORMATETC, "IDataObject_GetData should have failed with DV_E_FORMATETC instead of 0x%08x\n", hr);

    InitFormatEtc(fmtetc, CF_TEXT, TYMED_HGLOBAL);
    fmtetc.tymed = TYMED_FILE;
    hr = IDataObject_GetData(data_obj, &fmtetc, &stgmedium);
    ok(hr == DV_E_TYMED, "IDataObject_GetData should have failed with DV_E_TYMED instead of 0x%08x\n", hr);

    ok(DataObjectImpl_GetData_calls == 6, "DataObjectImpl_GetData should have been called 6 times instead of %d times\n", DataObjectImpl_GetData_calls);

    IDataObject_Release(data_obj);
}

static void test_enum_fmtetc(IDataObject *src)
{
    HRESULT hr;
    IDataObject *data;
    IEnumFORMATETC *enum_fmt, *src_enum;
    FORMATETC fmt, src_fmt;
    DWORD count = 0;

    hr = OleGetClipboard(&data);
    ok(hr == S_OK, "OleGetClipboard failed with error 0x%08x\n", hr);

    hr = IDataObject_EnumFormatEtc(data, DATADIR_SET, &enum_fmt);
    ok(hr == E_NOTIMPL ||
       broken(hr == E_INVALIDARG), /* win98 (not win98SE) */
       "got %08x\n", hr);

    DataObjectImpl_EnumFormatEtc_calls = 0;
    hr = IDataObject_EnumFormatEtc(data, DATADIR_GET, &enum_fmt);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(DataObjectImpl_EnumFormatEtc_calls == 0, "EnumFormatEtc was called\n");

    if(src) IDataObject_EnumFormatEtc(src, DATADIR_GET, &src_enum);

    while((hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL)) == S_OK)
    {
        ok(src != NULL, "shouldn't be here\n");
        hr = IEnumFORMATETC_Next(src_enum, 1, &src_fmt, NULL);
        ok(hr == S_OK, "%d: got %08x\n", count, hr);
        trace("%d: %s\n", count, dump_fmtetc(&fmt));
        ok(fmt.cfFormat == src_fmt.cfFormat, "%d: %04x %04x\n", count, fmt.cfFormat, src_fmt.cfFormat);
        ok(fmt.dwAspect == src_fmt.dwAspect, "%d: %08x %08x\n", count, fmt.dwAspect, src_fmt.dwAspect);
        ok(fmt.lindex == src_fmt.lindex, "%d: %08x %08x\n", count, fmt.lindex, src_fmt.lindex);
        ok(fmt.tymed == src_fmt.tymed, "%d: %08x %08x\n", count, fmt.tymed, src_fmt.tymed);
        if(fmt.ptd)
        {
            ok(src_fmt.ptd != NULL, "%d: expected non-NULL\n", count);
            CoTaskMemFree(fmt.ptd);
            CoTaskMemFree(src_fmt.ptd);
        }
        count++;
    }

    ok(hr == S_FALSE, "%d: got %08x\n", count, hr);

    if(src)
    {
        hr = IEnumFORMATETC_Next(src_enum, 1, &src_fmt, NULL);
        ok(hr == S_FALSE ||
           broken(hr == S_OK && count == 5), /* win9x and winme don't enumerate duplicated cf's */
           "%d: got %08x\n", count, hr);
        IEnumFORMATETC_Release(src_enum);
    }

    hr = IEnumFORMATETC_Reset(enum_fmt);
    ok(hr == S_OK, "got %08x\n", hr);

    if(src) /* Exercise the enumerator a bit */
    {
        IEnumFORMATETC *clone;
        FORMATETC third_fmt;

        hr = IEnumFORMATETC_Next(enum_fmt, 1, &third_fmt, NULL);
        ok(hr == S_OK, "got %08x\n", hr);
        hr = IEnumFORMATETC_Next(enum_fmt, 1, &third_fmt, NULL);
        ok(hr == S_OK, "got %08x\n", hr);
        hr = IEnumFORMATETC_Next(enum_fmt, 1, &third_fmt, NULL);
        ok(hr == S_OK, "got %08x\n", hr);

        hr = IEnumFORMATETC_Reset(enum_fmt);
        ok(hr == S_OK, "got %08x\n", hr);
        hr = IEnumFORMATETC_Skip(enum_fmt, 2);
        ok(hr == S_OK, "got %08x\n", hr);

        hr = IEnumFORMATETC_Clone(enum_fmt, &clone);
        ok(hr == S_OK, "got %08x\n", hr);
        hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
        ok(hr == S_OK, "got %08x\n", hr);
        ok(fmt.cfFormat == third_fmt.cfFormat, "formats don't match\n");
        hr = IEnumFORMATETC_Next(clone, 1, &fmt, NULL);
        ok(hr == S_OK, "got %08x\n", hr);
        ok(fmt.cfFormat == third_fmt.cfFormat, "formats don't match\n");
        IEnumFORMATETC_Release(clone);
    }

    IEnumFORMATETC_Release(enum_fmt);
    IDataObject_Release(data);
}

static void test_no_cf_dataobject(void)
{
    UINT cf_dataobject = RegisterClipboardFormatA("DataObject");
    UINT cf_ole_priv_data = RegisterClipboardFormatA("Ole Private Data");
    HANDLE h;
    OpenClipboard(NULL);

    h = GetClipboardData(cf_dataobject);
    ok(!h, "got %p\n", h);
    h = GetClipboardData(cf_ole_priv_data);
    ok(!h, "got %p\n", h);

    CloseClipboard();
}

static void test_cf_dataobject(IDataObject *data)
{
    UINT cf = 0;
    UINT cf_dataobject = RegisterClipboardFormatA("DataObject");
    UINT cf_ole_priv_data = RegisterClipboardFormatA("Ole Private Data");
    BOOL found_dataobject = FALSE, found_priv_data = FALSE;

    OpenClipboard(NULL);
    do
    {
        cf = EnumClipboardFormats(cf);
        if(cf == cf_dataobject)
        {
            HGLOBAL h = GetClipboardData(cf);
            HWND *ptr = GlobalLock(h);
            DWORD size = GlobalSize(h);
            HWND clip_owner = GetClipboardOwner();

            found_dataobject = TRUE;
            ok(size >= sizeof(*ptr), "size %d\n", size);
            if(data)
                ok(*ptr == clip_owner, "hwnd %p clip_owner %p\n", *ptr, clip_owner);
            else /* ole clipboard flushed */
                ok(*ptr == NULL, "hwnd %p\n", *ptr);
            GlobalUnlock(h);
        }
        else if(cf == cf_ole_priv_data)
        {
            found_priv_data = TRUE;
            if(data)
            {
                HGLOBAL h = GetClipboardData(cf);
                DWORD *ptr = GlobalLock(h);
                DWORD size = GlobalSize(h);

                if(size != ptr[1])
                    win_skip("Ole Private Data in win9x format\n");
                else
                {
                    HRESULT hr;
                    IEnumFORMATETC *enum_fmt;
                    DWORD count = 0;
                    FORMATETC fmt;
                    struct formatetcetc
                    {
                        FORMATETC fmt;
                        BOOL first_use_of_cf;
                        DWORD res[2];
                    } *fmt_ptr;
                    struct priv_data
                    {
                        DWORD res1;
                        DWORD size;
                        DWORD res2;
                        DWORD count;
                        DWORD res3[2];
                        struct formatetcetc fmts[1];
                    } *priv = (struct priv_data*)ptr;
                    CLIPFORMAT cfs_seen[10];

                    hr = IDataObject_EnumFormatEtc(data, DATADIR_GET, &enum_fmt);
                    ok(hr == S_OK, "got %08x\n", hr);
                    fmt_ptr = priv->fmts;

                    while(IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL) == S_OK)
                    {
                        int i;
                        BOOL seen_cf = FALSE;

                        ok(fmt_ptr->fmt.cfFormat == fmt.cfFormat,
                           "got %08x expected %08x\n", fmt_ptr->fmt.cfFormat, fmt.cfFormat);
                        ok(fmt_ptr->fmt.dwAspect == fmt.dwAspect, "got %08x expected %08x\n",
                           fmt_ptr->fmt.dwAspect, fmt.dwAspect);
                        ok(fmt_ptr->fmt.lindex == fmt.lindex, "got %08x expected %08x\n",
                           fmt_ptr->fmt.lindex, fmt.lindex);
                        ok(fmt_ptr->fmt.tymed == fmt.tymed, "got %08x expected %08x\n",
                           fmt_ptr->fmt.tymed, fmt.tymed);
                        for(i = 0; i < count; i++)
                            if(fmt_ptr->fmt.cfFormat == cfs_seen[i])
                            {
                                seen_cf = TRUE;
                                break;
                            }
                        cfs_seen[count] = fmt.cfFormat;
                        ok(fmt_ptr->first_use_of_cf == seen_cf ? FALSE : TRUE, "got %08x expected %08x\n",
                           fmt_ptr->first_use_of_cf, !seen_cf);
                        ok(fmt_ptr->res[0] == 0, "got %08x\n", fmt_ptr->res[1]);
                        ok(fmt_ptr->res[1] == 0, "got %08x\n", fmt_ptr->res[2]);
                        if(fmt.ptd)
                        {
                            DVTARGETDEVICE *target;

                            ok(fmt_ptr->fmt.ptd != NULL, "target device offset zero\n");
                            target = (DVTARGETDEVICE*)((char*)priv + (DWORD_PTR)fmt_ptr->fmt.ptd);
                            ok(!memcmp(target, fmt.ptd, fmt.ptd->tdSize), "target devices differ\n");
                            CoTaskMemFree(fmt.ptd);
                        }
                        fmt_ptr++;
                        count++;
                    }
                    ok(priv->res1 == 0, "got %08x\n", priv->res1);
                    ok(priv->res2 == 1, "got %08x\n", priv->res2);
                    ok(priv->count == count, "got %08x expected %08x\n", priv->count, count);
                    ok(priv->res3[0] == 0, "got %08x\n", priv->res3[0]);

                    /* win64 sets the lsb */
                    if(sizeof(fmt_ptr->fmt.ptd) == 8)
                        todo_wine ok(priv->res3[1] == 1, "got %08x\n", priv->res3[1]);
                    else
                        ok(priv->res3[1] == 0, "got %08x\n", priv->res3[1]);

                    GlobalUnlock(h);
                    IEnumFORMATETC_Release(enum_fmt);
                }
            }
        }
        else if(cf == cf_stream)
        {
            HGLOBAL h;
            void *ptr;
            DWORD size;

            DataObjectImpl_GetDataHere_calls = 0;
            h = GetClipboardData(cf);
            ok(DataObjectImpl_GetDataHere_calls == 1, "got %d\n", DataObjectImpl_GetDataHere_calls);
            ptr = GlobalLock(h);
            size = GlobalSize(h);
            ok(size == strlen(cmpl_stm_data) ||
               broken(size > strlen(cmpl_stm_data)), /* win9x, winme */
               "expected %d got %d\n", lstrlenA(cmpl_stm_data), size);
            ok(!memcmp(ptr, cmpl_stm_data, strlen(cmpl_stm_data)), "mismatch\n");
            GlobalUnlock(h);
        }
        else if(cf == cf_global)
        {
            HGLOBAL h;
            void *ptr;
            DWORD size;

            DataObjectImpl_GetDataHere_calls = 0;
            h = GetClipboardData(cf);
            ok(DataObjectImpl_GetDataHere_calls == 0, "got %d\n", DataObjectImpl_GetDataHere_calls);
            ptr = GlobalLock(h);
            size = GlobalSize(h);
            ok(size == strlen(cmpl_text_data) + 1 ||
               broken(size > strlen(cmpl_text_data) + 1), /* win9x, winme */
               "expected %d got %d\n", lstrlenA(cmpl_text_data) + 1, size);
            ok(!memcmp(ptr, cmpl_text_data, strlen(cmpl_text_data) + 1), "mismatch\n");
            GlobalUnlock(h);
        }
    } while(cf);
    CloseClipboard();
    ok(found_dataobject, "didn't find cf_dataobject\n");
    ok(found_priv_data, "didn't find cf_ole_priv_data\n");
}

static void test_set_clipboard(void)
{
    HRESULT hr;
    ULONG ref;
    LPDATAOBJECT data1, data2, data_cmpl;
    HGLOBAL hblob, h;

    cf_stream = RegisterClipboardFormatA("stream format");
    cf_storage = RegisterClipboardFormatA("storage format");
    cf_global = RegisterClipboardFormatA("global format");
    cf_another = RegisterClipboardFormatA("another format");
    cf_onemore = RegisterClipboardFormatA("one more format");

    hr = DataObjectImpl_CreateText("data1", &data1);
    ok(SUCCEEDED(hr), "Failed to create data1 object: 0x%08x\n", hr);
    if(FAILED(hr))
        return;
    hr = DataObjectImpl_CreateText("data2", &data2);
    ok(SUCCEEDED(hr), "Failed to create data2 object: 0x%08x\n", hr);
    if(FAILED(hr))
        return;
    hr = DataObjectImpl_CreateComplex(&data_cmpl);
    ok(SUCCEEDED(hr), "Failed to create complex data object: 0x%08x\n", hr);
    if(FAILED(hr))
        return;

    hr = OleSetClipboard(data1);
    ok(hr == CO_E_NOTINITIALIZED, "OleSetClipboard should have failed with CO_E_NOTINITIALIZED instead of 0x%08x\n", hr);

    CoInitialize(NULL);
    hr = OleSetClipboard(data1);
    ok(hr == CO_E_NOTINITIALIZED ||
       hr == CLIPBRD_E_CANT_SET, /* win9x */
       "OleSetClipboard should have failed with "
       "CO_E_NOTINITIALIZED or CLIPBRD_E_CANT_SET instead of 0x%08x\n", hr);
    CoUninitialize();

    hr = OleInitialize(NULL);
    ok(hr == S_OK, "OleInitialize failed with error 0x%08x\n", hr);

    hr = OleSetClipboard(data1);
    ok(hr == S_OK, "failed to set clipboard to data1, hr = 0x%08x\n", hr);

    test_cf_dataobject(data1);

    hr = OleIsCurrentClipboard(data1);
    ok(hr == S_OK, "expected current clipboard to be data1, hr = 0x%08x\n", hr);
    hr = OleIsCurrentClipboard(data2);
    ok(hr == S_FALSE, "did not expect current clipboard to be data2, hr = 0x%08x\n", hr);
    hr = OleIsCurrentClipboard(NULL);
    ok(hr == S_FALSE, "expect S_FALSE, hr = 0x%08x\n", hr);

    test_get_clipboard();

    hr = OleSetClipboard(data2);
    ok(hr == S_OK, "failed to set clipboard to data2, hr = 0x%08x\n", hr);
    hr = OleIsCurrentClipboard(data1);
    ok(hr == S_FALSE, "did not expect current clipboard to be data1, hr = 0x%08x\n", hr);
    hr = OleIsCurrentClipboard(data2);
    ok(hr == S_OK, "expected current clipboard to be data2, hr = 0x%08x\n", hr);
    hr = OleIsCurrentClipboard(NULL);
    ok(hr == S_FALSE, "expect S_FALSE, hr = 0x%08x\n", hr);

    /* put a format directly onto the clipboard to show
       OleFlushClipboard doesn't empty the clipboard */
    hblob = GlobalAlloc(GMEM_DDESHARE|GMEM_MOVEABLE|GMEM_ZEROINIT, 10);
    OpenClipboard(NULL);
    h = SetClipboardData(cf_onemore, hblob);
    ok(h == hblob, "got %p\n", h);
    h = GetClipboardData(cf_onemore);
    ok(h == hblob ||
       broken(h != NULL), /* win9x */
       "got %p\n", h);
    CloseClipboard();

    hr = OleFlushClipboard();
    ok(hr == S_OK, "failed to flush clipboard, hr = 0x%08x\n", hr);
    hr = OleIsCurrentClipboard(data1);
    ok(hr == S_FALSE, "did not expect current clipboard to be data1, hr = 0x%08x\n", hr);
    hr = OleIsCurrentClipboard(data2);
    ok(hr == S_FALSE, "did not expect current clipboard to be data2, hr = 0x%08x\n", hr);
    hr = OleIsCurrentClipboard(NULL);
    ok(hr == S_FALSE, "expect S_FALSE, hr = 0x%08x\n", hr);

    /* format should survive the flush */
    OpenClipboard(NULL);
    h = GetClipboardData(cf_onemore);
    ok(h == hblob ||
       broken(h != NULL), /* win9x */
       "got %p\n", h);
    CloseClipboard();

    test_cf_dataobject(NULL);

    ok(OleSetClipboard(NULL) == S_OK, "failed to clear clipboard, hr = 0x%08x\n", hr);

    OpenClipboard(NULL);
    h = GetClipboardData(cf_onemore);
    ok(h == NULL, "got %p\n", h);
    CloseClipboard();

    trace("setting complex\n");
    hr = OleSetClipboard(data_cmpl);
    ok(hr == S_OK, "failed to set clipboard to complex data, hr = 0x%08x\n", hr);
    test_cf_dataobject(data_cmpl);
    test_enum_fmtetc(data_cmpl);

    ok(OleSetClipboard(NULL) == S_OK, "failed to clear clipboard, hr = 0x%08x\n", hr);

    test_no_cf_dataobject();
    test_enum_fmtetc(NULL);

    ref = IDataObject_Release(data1);
    ok(ref == 0, "expected data1 ref=0, got %d\n", ref);
    ref = IDataObject_Release(data2);
    ok(ref == 0, "expected data2 ref=0, got %d\n", ref);
    ref = IDataObject_Release(data_cmpl);
    ok(ref == 0, "expected data_cmpl ref=0, got %d\n", ref);

    OleUninitialize();
}

static inline ULONG count_refs(IDataObject *d)
{
    IDataObject_AddRef(d);
    return IDataObject_Release(d);
}

static void test_consumer_refs(void)
{
    HRESULT hr;
    IDataObject *src, *src2, *get1, *get2, *get3;
    ULONG refs, old_refs;
    FORMATETC fmt;
    STGMEDIUM med;

    InitFormatEtc(fmt, CF_TEXT, TYMED_HGLOBAL);

    OleInitialize(NULL);

    /* First show that each clipboard state results in
       a different data object */

    hr = DataObjectImpl_CreateText("data1", &src);
    ok(hr == S_OK, "got %08x\n", hr);
    hr = DataObjectImpl_CreateText("data2", &src2);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = OleSetClipboard(src);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = OleGetClipboard(&get1);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = OleGetClipboard(&get2);
    ok(hr == S_OK, "got %08x\n", hr);

    ok(get1 == get2 ||
       broken(get1 != get2), /* win9x, winme & nt4 */
       "data objects differ\n");
    refs = IDataObject_Release(get2);
    ok(refs == (get1 == get2 ? 1 : 0), "got %d\n", refs);

    OleFlushClipboard();

    DataObjectImpl_GetData_calls = 0;
    hr = IDataObject_GetData(get1, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(DataObjectImpl_GetData_calls == 0, "GetData called\n");
    ReleaseStgMedium(&med);

    hr = OleGetClipboard(&get2);
    ok(hr == S_OK, "got %08x\n", hr);

    ok(get1 != get2, "data objects match\n");

    OleSetClipboard(NULL);

    hr = OleGetClipboard(&get3);
    ok(hr == S_OK, "got %08x\n", hr);

    ok(get1 != get3, "data objects match\n");
    ok(get2 != get3, "data objects match\n");

    IDataObject_Release(get3);
    IDataObject_Release(get2);
    IDataObject_Release(get1);

    /* Now call GetData before the flush and show that this
       takes a ref on our src data obj. */

    hr = OleSetClipboard(src);
    ok(hr == S_OK, "got %08x\n", hr);

    old_refs = count_refs(src);

    hr = OleGetClipboard(&get1);
    ok(hr == S_OK, "got %08x\n", hr);

    refs = count_refs(src);
    ok(refs == old_refs, "%d %d\n", refs, old_refs);

    DataObjectImpl_GetData_calls = 0;
    hr = IDataObject_GetData(get1, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(DataObjectImpl_GetData_calls == 1, "GetData not called\n");
    ReleaseStgMedium(&med);
    refs = count_refs(src);
    ok(refs == old_refs + 1, "%d %d\n", refs, old_refs);

    OleFlushClipboard();

    DataObjectImpl_GetData_calls = 0;
    hr = IDataObject_GetData(get1, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(DataObjectImpl_GetData_calls == 1, "GetData not called\n");
    ReleaseStgMedium(&med);

    refs = count_refs(src);
    ok(refs == 2, "%d\n", refs);

    IDataObject_Release(get1);

    refs = count_refs(src);
    ok(refs == 1, "%d\n", refs);

    /* Now set a second src object before the call to GetData
       and show that GetData calls that second src. */

    hr = OleSetClipboard(src);
    ok(hr == S_OK, "got %08x\n", hr);

    old_refs = count_refs(src);

    hr = OleGetClipboard(&get1);
    ok(hr == S_OK, "got %08x\n", hr);

    refs = count_refs(src);
    ok(refs == old_refs, "%d %d\n", refs, old_refs);

    hr = OleSetClipboard(src2);
    ok(hr == S_OK, "got %08x\n", hr);

    old_refs = count_refs(src2);

    DataObjectImpl_GetData_calls = 0;
    hr = IDataObject_GetData(get1, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(DataObjectImpl_GetData_calls == 1, "GetData not called\n");
    ReleaseStgMedium(&med);

    refs = count_refs(src);
    ok(refs == 1, "%d\n", refs);
    refs = count_refs(src2);
    ok(refs == old_refs + 1, "%d %d\n", refs, old_refs);

    OleSetClipboard(NULL);

    refs = count_refs(src2);
    ok(refs == 2, "%d\n", refs);

    IDataObject_Release(get1);

    IDataObject_Release(src2);
    IDataObject_Release(src);

    OleUninitialize();
}

static void test_flushed_getdata(void)
{
    HRESULT hr;
    IDataObject *src, *get;
    FORMATETC fmt;
    STGMEDIUM med;
    STATSTG stat;
    DEVMODEW dm;

    OleInitialize(NULL);

    hr = DataObjectImpl_CreateComplex(&src);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = OleSetClipboard(src);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = OleFlushClipboard();
    ok(hr == S_OK, "got %08x\n", hr);

    hr = OleGetClipboard(&get);
    ok(hr == S_OK, "got %08x\n", hr);

    /* global format -> global & stream */

    InitFormatEtc(fmt, CF_TEXT, TYMED_HGLOBAL);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    InitFormatEtc(fmt, CF_TEXT, TYMED_ISTREAM);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTREAM, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    InitFormatEtc(fmt, CF_TEXT, TYMED_ISTORAGE);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == E_FAIL, "got %08x\n", hr);

    InitFormatEtc(fmt, CF_TEXT, 0xffff);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    /* stream format -> global & stream */

    InitFormatEtc(fmt, cf_stream, TYMED_ISTREAM);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTREAM, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    InitFormatEtc(fmt, cf_stream, TYMED_ISTORAGE);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == E_FAIL, "got %08x\n", hr);

    InitFormatEtc(fmt, cf_stream, TYMED_HGLOBAL);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    InitFormatEtc(fmt, cf_stream, 0xffff);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTREAM, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    /* storage format -> global, stream & storage */

    InitFormatEtc(fmt, cf_storage, TYMED_ISTORAGE);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTORAGE, "got %x\n", med.tymed);
    hr = IStorage_Stat(med.u.pstg, &stat, STATFLAG_NONAME);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(stat.grfMode == (STGM_SHARE_EXCLUSIVE | STGM_READWRITE), "got %08x\n", stat.grfMode);
    ReleaseStgMedium(&med);

    InitFormatEtc(fmt, cf_storage, TYMED_ISTREAM);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTREAM, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    InitFormatEtc(fmt, cf_storage, TYMED_HGLOBAL);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    InitFormatEtc(fmt, cf_storage, TYMED_HGLOBAL | TYMED_ISTREAM);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    InitFormatEtc(fmt, cf_storage, 0xffff);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTORAGE, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    /* complex format with target device */

    InitFormatEtc(fmt, cf_another, 0xffff);
    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == DV_E_FORMATETC ||
       broken(hr == S_OK), /* win9x, winme & nt4 */
       "got %08x\n", hr);
    if(hr == S_OK) ReleaseStgMedium(&med);

    InitFormatEtc(fmt, cf_another, 0xffff);
    memset(&dm, 0, sizeof(dm));
    dm.dmSize = sizeof(dm);
    dm.dmDriverExtra = 0;
    lstrcpyW(dm.dmDeviceName, device_name);
    fmt.ptd = HeapAlloc(GetProcessHeap(), 0, FIELD_OFFSET(DVTARGETDEVICE, tdData) + sizeof(device_name) + dm.dmSize + dm.dmDriverExtra);
    fmt.ptd->tdSize = FIELD_OFFSET(DVTARGETDEVICE, tdData) + sizeof(device_name) + dm.dmSize + dm.dmDriverExtra;
    fmt.ptd->tdDriverNameOffset = FIELD_OFFSET(DVTARGETDEVICE, tdData);
    fmt.ptd->tdDeviceNameOffset = 0;
    fmt.ptd->tdPortNameOffset   = 0;
    fmt.ptd->tdExtDevmodeOffset = fmt.ptd->tdDriverNameOffset + sizeof(device_name);
    lstrcpyW((WCHAR*)fmt.ptd->tdData, device_name);
    memcpy(fmt.ptd->tdData + sizeof(device_name), &dm, dm.dmSize + dm.dmDriverExtra);

    hr = IDataObject_GetData(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTORAGE, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);

    HeapFree(GetProcessHeap(), 0, fmt.ptd);


    IDataObject_Release(get);
    IDataObject_Release(src);
    OleUninitialize();
}

static HGLOBAL create_text(void)
{
    HGLOBAL h = GlobalAlloc(GMEM_DDESHARE|GMEM_MOVEABLE, 5);
    char *p = GlobalLock(h);
    strcpy(p, "test");
    GlobalUnlock(h);
    return h;
}

static HENHMETAFILE create_emf(void)
{
    const RECT rect = {0, 0, 100, 100};
    HDC hdc = CreateEnhMetaFileA(NULL, NULL, &rect, "HENHMETAFILE Ole Clipboard Test\0Test\0\0");
    ExtTextOutA(hdc, 0, 0, ETO_OPAQUE, &rect, "Test String", strlen("Test String"), NULL);
    return CloseEnhMetaFile(hdc);
}

static void test_nonole_clipboard(void)
{
    HRESULT hr;
    BOOL r;
    IDataObject *get;
    IEnumFORMATETC *enum_fmt;
    FORMATETC fmt;
    HGLOBAL h, hblob, htext;
    HENHMETAFILE emf;

    r = OpenClipboard(NULL);
    ok(r, "gle %d\n", GetLastError());
    r = EmptyClipboard();
    ok(r, "gle %d\n", GetLastError());
    r = CloseClipboard();
    ok(r, "gle %d\n", GetLastError());

    OleInitialize(NULL);

    /* empty clipboard */
    hr = OleGetClipboard(&get);
    ok(hr == S_OK, "got %08x\n", hr);
    hr = IDataObject_EnumFormatEtc(get, DATADIR_GET, &enum_fmt);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
    ok(hr == S_FALSE, "got %08x\n", hr);
    IEnumFORMATETC_Release(enum_fmt);

    IDataObject_Release(get);

    /* set a user defined clipboard type */

    htext = create_text();
    hblob = GlobalAlloc(GMEM_DDESHARE|GMEM_MOVEABLE|GMEM_ZEROINIT, 10);
    emf = create_emf();

    r = OpenClipboard(NULL);
    ok(r, "gle %d\n", GetLastError());
    h = SetClipboardData(CF_TEXT, htext);
    ok(h == htext, "got %p\n", h);
    h = SetClipboardData(cf_onemore, hblob);
    ok(h == hblob, "got %p\n", h);
    h = SetClipboardData(CF_ENHMETAFILE, emf);
    ok(h == emf, "got %p\n", h);
    r = CloseClipboard();
    ok(r, "gle %d\n", GetLastError());

    hr = OleGetClipboard(&get);
    ok(hr == S_OK, "got %08x\n", hr);
    hr = IDataObject_EnumFormatEtc(get, DATADIR_GET, &enum_fmt);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(fmt.cfFormat == CF_TEXT, "cf %04x\n", fmt.cfFormat);
    ok(fmt.ptd == NULL, "ptd %p\n", fmt.ptd);
    ok(fmt.dwAspect == DVASPECT_CONTENT, "aspect %x\n", fmt.dwAspect);
    ok(fmt.lindex == -1, "lindex %d\n", fmt.lindex);
    ok(fmt.tymed == (TYMED_ISTREAM | TYMED_HGLOBAL), "tymed %x\n", fmt.tymed);

    hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(fmt.cfFormat == cf_onemore, "cf %04x\n", fmt.cfFormat);
    ok(fmt.ptd == NULL, "ptd %p\n", fmt.ptd);
    ok(fmt.dwAspect == DVASPECT_CONTENT, "aspect %x\n", fmt.dwAspect);
    ok(fmt.lindex == -1, "lindex %d\n", fmt.lindex);
    ok(fmt.tymed == (TYMED_ISTREAM | TYMED_HGLOBAL), "tymed %x\n", fmt.tymed);

    hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(fmt.cfFormat == CF_ENHMETAFILE, "cf %04x\n", fmt.cfFormat);
    ok(fmt.ptd == NULL, "ptd %p\n", fmt.ptd);
    ok(fmt.dwAspect == DVASPECT_CONTENT, "aspect %x\n", fmt.dwAspect);
    ok(fmt.lindex == -1, "lindex %d\n", fmt.lindex);
    ok(fmt.tymed == TYMED_ENHMF, "tymed %x\n", fmt.tymed);

    hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
    ok(hr == S_OK, "got %08x\n", hr); /* User32 adds some synthesised formats */

    todo_wine ok(fmt.cfFormat == CF_LOCALE, "cf %04x\n", fmt.cfFormat);
    if(fmt.cfFormat == CF_LOCALE)
    {
        ok(fmt.ptd == NULL, "ptd %p\n", fmt.ptd);
        ok(fmt.dwAspect == DVASPECT_CONTENT, "aspect %x\n", fmt.dwAspect);
        ok(fmt.lindex == -1, "lindex %d\n", fmt.lindex);
        ok(fmt.tymed == (TYMED_ISTREAM | TYMED_HGLOBAL), "tymed %x\n", fmt.tymed);

        hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
        ok(hr == S_OK, "got %08x\n", hr);
    }

    ok(fmt.cfFormat == CF_OEMTEXT, "cf %04x\n", fmt.cfFormat);
    ok(fmt.ptd == NULL, "ptd %p\n", fmt.ptd);
    ok(fmt.dwAspect == DVASPECT_CONTENT, "aspect %x\n", fmt.dwAspect);
    ok(fmt.lindex == -1, "lindex %d\n", fmt.lindex);
    ok(fmt.tymed == (TYMED_ISTREAM | TYMED_HGLOBAL), "tymed %x\n", fmt.tymed);

    hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(fmt.cfFormat == CF_UNICODETEXT ||
       broken(fmt.cfFormat == CF_METAFILEPICT), /* win9x and winme don't have CF_UNICODETEXT */
       "cf %04x\n", fmt.cfFormat);
    if(fmt.cfFormat == CF_UNICODETEXT)
    {
        ok(fmt.ptd == NULL, "ptd %p\n", fmt.ptd);
        ok(fmt.dwAspect == DVASPECT_CONTENT, "aspect %x\n", fmt.dwAspect);
        ok(fmt.lindex == -1, "lindex %d\n", fmt.lindex);
        ok(fmt.tymed == (TYMED_ISTREAM | TYMED_HGLOBAL), "tymed %x\n", fmt.tymed);

        hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
        ok(hr == S_OK, "got %08x\n", hr);
    }
    ok(fmt.cfFormat == CF_METAFILEPICT, "cf %04x\n", fmt.cfFormat);
    ok(fmt.ptd == NULL, "ptd %p\n", fmt.ptd);
    ok(fmt.dwAspect == DVASPECT_CONTENT, "aspect %x\n", fmt.dwAspect);
    ok(fmt.lindex == -1, "lindex %d\n", fmt.lindex);
    ok(fmt.tymed == TYMED_MFPICT, "tymed %x\n", fmt.tymed);

    hr = IEnumFORMATETC_Next(enum_fmt, 1, &fmt, NULL);
    ok(hr == S_FALSE, "got %08x\n", hr);
    IEnumFORMATETC_Release(enum_fmt);

    IDataObject_Release(get);

    r = OpenClipboard(NULL);
    ok(r, "gle %d\n", GetLastError());
    r = EmptyClipboard();
    ok(r, "gle %d\n", GetLastError());
    r = CloseClipboard();
    ok(r, "gle %d\n", GetLastError());

    OleUninitialize();
}

static void test_getdatahere(void)
{
    HRESULT hr;
    IDataObject *src, *get;
    FORMATETC fmt;
    STGMEDIUM med;

    OleInitialize(NULL);

    hr = DataObjectImpl_CreateComplex(&src);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = OleSetClipboard(src);
    ok(hr == S_OK, "got %08x\n", hr);

    hr = OleGetClipboard(&get);
    ok(hr == S_OK, "got %08x\n", hr);

    /* global format -> global & stream */

    DataObjectImpl_GetData_calls = 0;
    DataObjectImpl_GetDataHere_calls = 0;

    InitFormatEtc(fmt, CF_TEXT, TYMED_HGLOBAL);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_HGLOBAL;
    U(med).hGlobal = GlobalAlloc(GMEM_MOVEABLE, 100);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 1, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 1, "called %d\n", DataObjectImpl_GetData_calls);

    InitFormatEtc(fmt, CF_TEXT, 0);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_HGLOBAL;
    U(med).hGlobal = GlobalAlloc(GMEM_MOVEABLE, 100);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 2, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 1, "called %d\n", DataObjectImpl_GetData_calls);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_HGLOBAL;
    U(med).hGlobal = GlobalAlloc(GMEM_MOVEABLE, 1);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == E_FAIL, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 3, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 1, "called %d\n", DataObjectImpl_GetData_calls);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_ISTREAM;
    CreateStreamOnHGlobal(NULL, TRUE, &U(med).pstm);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTREAM, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 4, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 1, "called %d\n", DataObjectImpl_GetData_calls);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_ISTORAGE;
    StgCreateDocfile(NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE, 0, &U(med).pstg);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == E_FAIL, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTORAGE, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 5, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 1, "called %d\n", DataObjectImpl_GetData_calls);

    InitFormatEtc(fmt, cf_stream, 0);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_HGLOBAL;
    U(med).hGlobal = GlobalAlloc(GMEM_MOVEABLE, 100);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 7, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 2, "called %d\n", DataObjectImpl_GetData_calls);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_ISTREAM;
    CreateStreamOnHGlobal(NULL, TRUE, &U(med).pstm);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTREAM, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 8, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 2, "called %d\n", DataObjectImpl_GetData_calls);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_ISTORAGE;
    StgCreateDocfile(NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE, 0, &U(med).pstg);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == E_FAIL, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTORAGE, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 9, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 2, "called %d\n", DataObjectImpl_GetData_calls);

    InitFormatEtc(fmt, cf_storage, 0);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_HGLOBAL;
    U(med).hGlobal = GlobalAlloc(GMEM_MOVEABLE, 3000);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_HGLOBAL, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 11, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 3, "called %d\n", DataObjectImpl_GetData_calls);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_ISTREAM;
    CreateStreamOnHGlobal(NULL, TRUE, &U(med).pstm);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTREAM, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 12, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 3, "called %d\n", DataObjectImpl_GetData_calls);

    med.pUnkForRelease = NULL;
    med.tymed = TYMED_ISTORAGE;
    StgCreateDocfile(NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE, 0, &U(med).pstg);
    hr = IDataObject_GetDataHere(get, &fmt, &med);
    ok(hr == S_OK, "got %08x\n", hr);
    ok(med.tymed == TYMED_ISTORAGE, "got %x\n", med.tymed);
    ReleaseStgMedium(&med);
    ok(DataObjectImpl_GetDataHere_calls == 13, "called %d\n", DataObjectImpl_GetDataHere_calls);
    ok(DataObjectImpl_GetData_calls == 3, "called %d\n", DataObjectImpl_GetData_calls);


    IDataObject_Release(get);
    IDataObject_Release(src);

    OleUninitialize();

}

START_TEST(clipboard)
{
    test_set_clipboard();
    test_consumer_refs();
    test_flushed_getdata();
    test_nonole_clipboard();
    test_getdatahere();
}