1223 lines
37 KiB
C
1223 lines
37 KiB
C
/*
|
|
* Mac clipboard driver
|
|
*
|
|
* Copyright 1994 Martin Ayotte
|
|
* 1996 Alex Korobka
|
|
* 1999 Noel Borthwick
|
|
* 2003 Ulrich Czekalla for CodeWeavers
|
|
* Copyright 2011, 2012, 2013 Ken Thomases for CodeWeavers Inc.
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "macdrv.h"
|
|
#include "winuser.h"
|
|
#include "wine/list.h"
|
|
#include "wine/server.h"
|
|
#include "wine/unicode.h"
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(clipboard);
|
|
|
|
|
|
/**************************************************************************
|
|
* Types
|
|
**************************************************************************/
|
|
|
|
typedef struct
|
|
{
|
|
HWND hwnd_owner;
|
|
UINT flags;
|
|
} CLIPBOARDINFO, *LPCLIPBOARDINFO;
|
|
|
|
typedef HANDLE (*DRVIMPORTFUNC)(CFDataRef data);
|
|
typedef CFDataRef (*DRVEXPORTFUNC)(HANDLE data);
|
|
|
|
typedef struct
|
|
{
|
|
struct list entry;
|
|
UINT format_id;
|
|
CFStringRef type;
|
|
DRVIMPORTFUNC import_func;
|
|
DRVEXPORTFUNC export_func;
|
|
BOOL synthesized;
|
|
} WINE_CLIPFORMAT;
|
|
|
|
|
|
/**************************************************************************
|
|
* Constants
|
|
**************************************************************************/
|
|
|
|
|
|
/**************************************************************************
|
|
* Forward Function Declarations
|
|
**************************************************************************/
|
|
|
|
static HANDLE import_clipboard_data(CFDataRef data);
|
|
static HANDLE import_oemtext_to_text(CFDataRef data);
|
|
static HANDLE import_oemtext_to_unicodetext(CFDataRef data);
|
|
static HANDLE import_text_to_oemtext(CFDataRef data);
|
|
static HANDLE import_text_to_unicodetext(CFDataRef data);
|
|
static HANDLE import_unicodetext_to_oemtext(CFDataRef data);
|
|
static HANDLE import_unicodetext_to_text(CFDataRef data);
|
|
static HANDLE import_utf8_to_oemtext(CFDataRef data);
|
|
static HANDLE import_utf8_to_text(CFDataRef data);
|
|
static HANDLE import_utf8_to_unicodetext(CFDataRef data);
|
|
|
|
static CFDataRef export_clipboard_data(HANDLE data);
|
|
static CFDataRef export_oemtext_to_utf8(HANDLE data);
|
|
static CFDataRef export_text_to_utf8(HANDLE data);
|
|
static CFDataRef export_unicodetext_to_utf8(HANDLE data);
|
|
|
|
|
|
/**************************************************************************
|
|
* Static Variables
|
|
**************************************************************************/
|
|
|
|
/* Clipboard formats */
|
|
static struct list format_list = LIST_INIT(format_list);
|
|
|
|
/* There are two naming schemes involved and we want to have a mapping between
|
|
them. There are Win32 clipboard format names and there are Mac pasteboard
|
|
types.
|
|
|
|
The Win32 standard clipboard formats don't have names, but they are associated
|
|
with Mac pasteboard types through the following tables, which are used to
|
|
initialize the format_list. Where possible, the standard clipboard formats
|
|
are mapped to predefined pasteboard type UTIs. Otherwise, we create Wine-
|
|
specific types of the form "org.winehq.builtin.<format>", where <format> is
|
|
the name of the symbolic constant for the format minus "CF_" and lowercased.
|
|
E.g. CF_BITMAP -> org.winehq.builtin.bitmap.
|
|
|
|
Win32 clipboard formats which originate in a Windows program may be registered
|
|
with an arbitrary name. We construct a Mac pasteboard type from these by
|
|
prepending "org.winehq.registered." to the registered name.
|
|
|
|
Likewise, Mac pasteboard types which originate in other apps may have
|
|
arbitrary type strings. We construct a Win32 clipboard format name from
|
|
these by prepending "org.winehq.mac-type." to the Mac pasteboard type.
|
|
|
|
Summary:
|
|
Win32 clipboard format names:
|
|
<none> standard clipboard format; maps via
|
|
format_list to either a predefined Mac UTI
|
|
or org.winehq.builtin.<format>.
|
|
org.winehq.mac-type.<Mac type> representation of Mac type in Win32 land;
|
|
maps to <Mac type>
|
|
<other> name registered within Win32 land; maps to
|
|
org.winehq.registered.<other>
|
|
Mac pasteboard type names:
|
|
org.winehq.builtin.<format ID> representation of Win32 standard clipboard
|
|
format for which there was no corresponding
|
|
predefined Mac UTI; maps via format_list
|
|
org.winehq.registered.<format name> representation of Win32 registered
|
|
clipboard format name; maps to <format name>
|
|
<other> Mac pasteboard type originating with system
|
|
or other apps; either maps via format_list
|
|
to a standard clipboard format or maps to
|
|
org.winehq.mac-type.<other>
|
|
*/
|
|
|
|
static const struct
|
|
{
|
|
UINT id;
|
|
CFStringRef type;
|
|
DRVIMPORTFUNC import;
|
|
DRVEXPORTFUNC export;
|
|
BOOL synthesized;
|
|
} builtin_format_ids[] =
|
|
{
|
|
{ CF_UNICODETEXT, CFSTR("org.winehq.builtin.unicodetext"), import_clipboard_data, export_clipboard_data, FALSE },
|
|
{ CF_TEXT, CFSTR("org.winehq.builtin.unicodetext"), import_unicodetext_to_text, NULL, TRUE },
|
|
{ CF_OEMTEXT, CFSTR("org.winehq.builtin.unicodetext"), import_unicodetext_to_oemtext, NULL, TRUE },
|
|
|
|
{ CF_TEXT, CFSTR("org.winehq.builtin.text"), import_clipboard_data, export_clipboard_data, FALSE },
|
|
{ CF_UNICODETEXT, CFSTR("org.winehq.builtin.text"), import_text_to_unicodetext, NULL, TRUE },
|
|
{ CF_OEMTEXT, CFSTR("org.winehq.builtin.text"), import_text_to_oemtext, NULL, TRUE },
|
|
|
|
{ CF_OEMTEXT, CFSTR("org.winehq.builtin.oemtext"), import_clipboard_data, export_clipboard_data, FALSE },
|
|
{ CF_UNICODETEXT, CFSTR("org.winehq.builtin.oemtext"), import_oemtext_to_unicodetext, NULL, TRUE },
|
|
{ CF_TEXT, CFSTR("org.winehq.builtin.oemtext"), import_oemtext_to_text, NULL, TRUE },
|
|
|
|
{ CF_UNICODETEXT, CFSTR("public.utf8-plain-text"), import_utf8_to_unicodetext, export_unicodetext_to_utf8, TRUE },
|
|
{ CF_TEXT, CFSTR("public.utf8-plain-text"), import_utf8_to_text, export_text_to_utf8, TRUE },
|
|
{ CF_OEMTEXT, CFSTR("public.utf8-plain-text"), import_utf8_to_oemtext, export_oemtext_to_utf8, TRUE },
|
|
};
|
|
|
|
/* The prefix prepended to an external Mac pasteboard type to make a Win32 clipboard format name. org.winehq.mac-type. */
|
|
static const WCHAR mac_type_name_prefix[] = {'o','r','g','.','w','i','n','e','h','q','.','m','a','c','-','t','y','p','e','.',0};
|
|
|
|
/* The prefix prepended to a Win32 clipboard format name to make a Mac pasteboard type. */
|
|
static const CFStringRef registered_name_type_prefix = CFSTR("org.winehq.registered.");
|
|
|
|
|
|
/**************************************************************************
|
|
* Internal Clipboard implementation methods
|
|
**************************************************************************/
|
|
|
|
/*
|
|
* format_list functions
|
|
*/
|
|
|
|
/**************************************************************************
|
|
* debugstr_format
|
|
*/
|
|
static const char *debugstr_format(UINT id)
|
|
{
|
|
WCHAR buffer[256];
|
|
|
|
if (GetClipboardFormatNameW(id, buffer, 256))
|
|
return wine_dbg_sprintf("0x%04x %s", id, debugstr_w(buffer));
|
|
|
|
switch (id)
|
|
{
|
|
#define BUILTIN(id) case id: return #id;
|
|
BUILTIN(CF_TEXT)
|
|
BUILTIN(CF_BITMAP)
|
|
BUILTIN(CF_METAFILEPICT)
|
|
BUILTIN(CF_SYLK)
|
|
BUILTIN(CF_DIF)
|
|
BUILTIN(CF_TIFF)
|
|
BUILTIN(CF_OEMTEXT)
|
|
BUILTIN(CF_DIB)
|
|
BUILTIN(CF_PALETTE)
|
|
BUILTIN(CF_PENDATA)
|
|
BUILTIN(CF_RIFF)
|
|
BUILTIN(CF_WAVE)
|
|
BUILTIN(CF_UNICODETEXT)
|
|
BUILTIN(CF_ENHMETAFILE)
|
|
BUILTIN(CF_HDROP)
|
|
BUILTIN(CF_LOCALE)
|
|
BUILTIN(CF_DIBV5)
|
|
BUILTIN(CF_OWNERDISPLAY)
|
|
BUILTIN(CF_DSPTEXT)
|
|
BUILTIN(CF_DSPBITMAP)
|
|
BUILTIN(CF_DSPMETAFILEPICT)
|
|
BUILTIN(CF_DSPENHMETAFILE)
|
|
#undef BUILTIN
|
|
default: return wine_dbg_sprintf("0x%04x", id);
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* insert_clipboard_format
|
|
*/
|
|
static WINE_CLIPFORMAT *insert_clipboard_format(UINT id, CFStringRef type)
|
|
{
|
|
WINE_CLIPFORMAT *format;
|
|
|
|
format = HeapAlloc(GetProcessHeap(), 0, sizeof(*format));
|
|
|
|
if (format == NULL)
|
|
{
|
|
WARN("No more memory for a new format!\n");
|
|
return NULL;
|
|
}
|
|
format->format_id = id;
|
|
format->import_func = import_clipboard_data;
|
|
format->export_func = export_clipboard_data;
|
|
format->synthesized = FALSE;
|
|
|
|
if (type)
|
|
format->type = CFStringCreateCopy(NULL, type);
|
|
else
|
|
{
|
|
WCHAR buffer[256];
|
|
|
|
GetClipboardFormatNameW(format->format_id, buffer, 256);
|
|
if (!strncmpW(buffer, mac_type_name_prefix, strlenW(mac_type_name_prefix)))
|
|
{
|
|
const WCHAR *p = buffer + strlenW(mac_type_name_prefix);
|
|
format->type = CFStringCreateWithCharacters(NULL, (UniChar*)p, strlenW(p));
|
|
}
|
|
else
|
|
{
|
|
format->type = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%S"),
|
|
registered_name_type_prefix, buffer);
|
|
}
|
|
}
|
|
|
|
list_add_tail(&format_list, &format->entry);
|
|
|
|
TRACE("Registering format %s type %s\n", debugstr_format(format->format_id),
|
|
debugstr_cf(format->type));
|
|
|
|
return format;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* register_format
|
|
*
|
|
* Register a custom Mac clipboard format.
|
|
*/
|
|
static WINE_CLIPFORMAT* register_format(UINT id, CFStringRef type)
|
|
{
|
|
WINE_CLIPFORMAT *format;
|
|
|
|
/* walk format chain to see if it's already registered */
|
|
LIST_FOR_EACH_ENTRY(format, &format_list, WINE_CLIPFORMAT, entry)
|
|
if (format->format_id == id) return format;
|
|
|
|
return insert_clipboard_format(id, type);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* format_for_type
|
|
*/
|
|
static WINE_CLIPFORMAT* format_for_type(WINE_CLIPFORMAT *current, CFStringRef type)
|
|
{
|
|
struct list *ptr = current ? ¤t->entry : &format_list;
|
|
WINE_CLIPFORMAT *format = NULL;
|
|
|
|
TRACE("current %p/%s type %s\n", current, debugstr_format(current ? current->format_id : 0), debugstr_cf(type));
|
|
|
|
while ((ptr = list_next(&format_list, ptr)))
|
|
{
|
|
format = LIST_ENTRY(ptr, WINE_CLIPFORMAT, entry);
|
|
if (CFEqual(format->type, type))
|
|
{
|
|
TRACE(" -> %p/%s\n", format, debugstr_format(format->format_id));
|
|
return format;
|
|
}
|
|
}
|
|
|
|
if (!current)
|
|
{
|
|
LPWSTR name;
|
|
|
|
if (CFStringHasPrefix(type, CFSTR("org.winehq.builtin.")))
|
|
{
|
|
ERR("Shouldn't happen. Built-in type %s should have matched something in format list.\n",
|
|
debugstr_cf(type));
|
|
return NULL;
|
|
}
|
|
else if (CFStringHasPrefix(type, registered_name_type_prefix))
|
|
{
|
|
int len = CFStringGetLength(type) - CFStringGetLength(registered_name_type_prefix);
|
|
name = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR));
|
|
CFStringGetCharacters(type, CFRangeMake(CFStringGetLength(registered_name_type_prefix), len),
|
|
(UniChar*)name);
|
|
name[len] = 0;
|
|
}
|
|
else
|
|
{
|
|
int len = strlenW(mac_type_name_prefix) + CFStringGetLength(type);
|
|
name = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR));
|
|
memcpy(name, mac_type_name_prefix, sizeof(mac_type_name_prefix));
|
|
CFStringGetCharacters(type, CFRangeMake(0, CFStringGetLength(type)),
|
|
(UniChar*)name + strlenW(mac_type_name_prefix));
|
|
name[len] = 0;
|
|
}
|
|
|
|
format = register_format(RegisterClipboardFormatW(name), type);
|
|
if (!format)
|
|
ERR("Failed to register format for type %s name %s\n", debugstr_cf(type), debugstr_w(name));
|
|
|
|
HeapFree(GetProcessHeap(), 0, name);
|
|
}
|
|
|
|
TRACE(" -> %p/%s\n", format, debugstr_format(format ? format->format_id : 0));
|
|
return format;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* convert_text
|
|
*
|
|
* Convert string data between code pages or to/from wide characters. The
|
|
* special value of (UINT)-1 for a code page indicates to use wide
|
|
* characters.
|
|
*/
|
|
static HANDLE convert_text(const void *src, int src_len, UINT src_cp, UINT dest_cp)
|
|
{
|
|
HANDLE ret = NULL;
|
|
const WCHAR *wstr;
|
|
int wstr_len;
|
|
HANDLE handle;
|
|
char *p;
|
|
|
|
if (src_cp == (UINT)-1)
|
|
{
|
|
wstr = src;
|
|
wstr_len = src_len / sizeof(WCHAR);
|
|
}
|
|
else
|
|
{
|
|
WCHAR *temp;
|
|
|
|
wstr_len = MultiByteToWideChar(src_cp, 0, src, src_len, NULL, 0);
|
|
if (!src_len || ((const char*)src)[src_len - 1]) wstr_len += 1;
|
|
temp = HeapAlloc(GetProcessHeap(), 0, wstr_len * sizeof(WCHAR));
|
|
MultiByteToWideChar(src_cp, 0, src, src_len, temp, wstr_len);
|
|
temp[wstr_len - 1] = 0;
|
|
wstr = temp;
|
|
}
|
|
|
|
if (dest_cp == (UINT)-1)
|
|
{
|
|
handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, wstr_len * sizeof(WCHAR));
|
|
if (handle && (p = GlobalLock(handle)))
|
|
{
|
|
memcpy(p, wstr, wstr_len * sizeof(WCHAR));
|
|
GlobalUnlock(handle);
|
|
ret = handle;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
INT len;
|
|
|
|
len = WideCharToMultiByte(dest_cp, 0, wstr, wstr_len, NULL, 0, NULL, NULL);
|
|
if (!wstr_len || wstr[wstr_len - 1]) len += 1;
|
|
handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, len);
|
|
|
|
if (handle && (p = GlobalLock(handle)))
|
|
{
|
|
WideCharToMultiByte(dest_cp, 0, wstr, wstr_len, p, len, NULL, NULL);
|
|
p[len - 1] = 0;
|
|
GlobalUnlock(handle);
|
|
ret = handle;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* convert_unicodetext_to_codepage
|
|
*/
|
|
static HANDLE convert_unicodetext_to_codepage(HANDLE unicode_handle, UINT cp)
|
|
{
|
|
LPWSTR unicode_string = GlobalLock(unicode_handle);
|
|
HANDLE ret = NULL;
|
|
|
|
if (unicode_string)
|
|
{
|
|
ret = convert_text(unicode_string, GlobalSize(unicode_handle), -1, cp);
|
|
GlobalUnlock(unicode_handle);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_clipboard_data
|
|
*
|
|
* Generic import clipboard data routine.
|
|
*/
|
|
static HANDLE import_clipboard_data(CFDataRef data)
|
|
{
|
|
HANDLE data_handle = NULL;
|
|
|
|
size_t len = CFDataGetLength(data);
|
|
if (len)
|
|
{
|
|
LPVOID p;
|
|
|
|
/* Turn on the DDESHARE flag to enable shared 32 bit memory */
|
|
data_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, len);
|
|
if (!data_handle)
|
|
return NULL;
|
|
|
|
if ((p = GlobalLock(data_handle)))
|
|
{
|
|
memcpy(p, CFDataGetBytePtr(data), len);
|
|
GlobalUnlock(data_handle);
|
|
}
|
|
else
|
|
{
|
|
GlobalFree(data_handle);
|
|
data_handle = NULL;
|
|
}
|
|
}
|
|
|
|
return data_handle;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_oemtext_to_text
|
|
*
|
|
* Import CF_OEMTEXT data, converting the string to CF_TEXT.
|
|
*/
|
|
static HANDLE import_oemtext_to_text(CFDataRef data)
|
|
{
|
|
return convert_text(CFDataGetBytePtr(data), CFDataGetLength(data), CP_OEMCP, CP_ACP);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_oemtext_to_unicodetext
|
|
*
|
|
* Import CF_OEMTEXT data, converting the string to CF_UNICODETEXT.
|
|
*/
|
|
static HANDLE import_oemtext_to_unicodetext(CFDataRef data)
|
|
{
|
|
return convert_text(CFDataGetBytePtr(data), CFDataGetLength(data), CP_OEMCP, -1);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_text_to_oemtext
|
|
*
|
|
* Import CF_TEXT data, converting the string to CF_OEMTEXT.
|
|
*/
|
|
static HANDLE import_text_to_oemtext(CFDataRef data)
|
|
{
|
|
return convert_text(CFDataGetBytePtr(data), CFDataGetLength(data), CP_ACP, CP_OEMCP);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_text_to_unicodetext
|
|
*
|
|
* Import CF_TEXT data, converting the string to CF_UNICODETEXT.
|
|
*/
|
|
static HANDLE import_text_to_unicodetext(CFDataRef data)
|
|
{
|
|
return convert_text(CFDataGetBytePtr(data), CFDataGetLength(data), CP_ACP, -1);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_unicodetext_to_oemtext
|
|
*
|
|
* Import a CF_UNICODETEXT string, converting the string to CF_OEMTEXT.
|
|
*/
|
|
static HANDLE import_unicodetext_to_oemtext(CFDataRef data)
|
|
{
|
|
return convert_text(CFDataGetBytePtr(data), CFDataGetLength(data), -1, CP_OEMCP);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_unicodetext_to_text
|
|
*
|
|
* Import a CF_UNICODETEXT string, converting the string to CF_TEXT.
|
|
*/
|
|
static HANDLE import_unicodetext_to_text(CFDataRef data)
|
|
{
|
|
return convert_text(CFDataGetBytePtr(data), CFDataGetLength(data), -1, CP_ACP);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_utf8_to_oemtext
|
|
*
|
|
* Import a UTF-8 string, converting the string to CF_OEMTEXT.
|
|
*/
|
|
static HANDLE import_utf8_to_oemtext(CFDataRef data)
|
|
{
|
|
HANDLE unicode_handle = import_utf8_to_unicodetext(data);
|
|
HANDLE ret = convert_unicodetext_to_codepage(unicode_handle, CP_OEMCP);
|
|
|
|
GlobalFree(unicode_handle);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_utf8_to_text
|
|
*
|
|
* Import a UTF-8 string, converting the string to CF_TEXT.
|
|
*/
|
|
static HANDLE import_utf8_to_text(CFDataRef data)
|
|
{
|
|
HANDLE unicode_handle = import_utf8_to_unicodetext(data);
|
|
HANDLE ret = convert_unicodetext_to_codepage(unicode_handle, CP_ACP);
|
|
|
|
GlobalFree(unicode_handle);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* import_utf8_to_unicodetext
|
|
*
|
|
* Import a UTF-8 string, converting the string to CF_UNICODETEXT.
|
|
*/
|
|
static HANDLE import_utf8_to_unicodetext(CFDataRef data)
|
|
{
|
|
const BYTE *src;
|
|
unsigned long data_len;
|
|
unsigned long new_lines = 0;
|
|
LPSTR dst;
|
|
unsigned long i, j;
|
|
HANDLE unicode_handle = NULL;
|
|
|
|
src = CFDataGetBytePtr(data);
|
|
data_len = CFDataGetLength(data);
|
|
for (i = 0; i < data_len; i++)
|
|
{
|
|
if (src[i] == '\n')
|
|
new_lines++;
|
|
}
|
|
|
|
if ((dst = HeapAlloc(GetProcessHeap(), 0, data_len + new_lines + 1)))
|
|
{
|
|
UINT count;
|
|
|
|
for (i = 0, j = 0; i < data_len; i++)
|
|
{
|
|
if (src[i] == '\n')
|
|
dst[j++] = '\r';
|
|
|
|
dst[j++] = src[i];
|
|
}
|
|
dst[j] = 0;
|
|
|
|
count = MultiByteToWideChar(CP_UTF8, 0, dst, -1, NULL, 0);
|
|
unicode_handle = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, count * sizeof(WCHAR));
|
|
|
|
if (unicode_handle)
|
|
{
|
|
WCHAR *textW = GlobalLock(unicode_handle);
|
|
MultiByteToWideChar(CP_UTF8, 0, dst, -1, textW, count);
|
|
GlobalUnlock(unicode_handle);
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, dst);
|
|
}
|
|
|
|
return unicode_handle;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* export_clipboard_data
|
|
*
|
|
* Generic export clipboard data routine.
|
|
*/
|
|
static CFDataRef export_clipboard_data(HANDLE data)
|
|
{
|
|
CFDataRef ret;
|
|
UINT len;
|
|
LPVOID src;
|
|
|
|
len = GlobalSize(data);
|
|
src = GlobalLock(data);
|
|
if (!src) return NULL;
|
|
|
|
ret = CFDataCreate(NULL, src, len);
|
|
GlobalUnlock(data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* export_codepage_to_utf8
|
|
*
|
|
* Export string data in a specified codepage to UTF-8.
|
|
*/
|
|
static CFDataRef export_codepage_to_utf8(HANDLE data, UINT cp)
|
|
{
|
|
CFDataRef ret = NULL;
|
|
const char* str;
|
|
|
|
if ((str = GlobalLock(data)))
|
|
{
|
|
HANDLE unicode = convert_text(str, GlobalSize(data), cp, -1);
|
|
|
|
ret = export_unicodetext_to_utf8(unicode);
|
|
|
|
GlobalFree(unicode);
|
|
GlobalUnlock(data);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* export_oemtext_to_utf8
|
|
*
|
|
* Export CF_OEMTEXT to UTF-8.
|
|
*/
|
|
static CFDataRef export_oemtext_to_utf8(HANDLE data)
|
|
{
|
|
return export_codepage_to_utf8(data, CP_OEMCP);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* export_text_to_utf8
|
|
*
|
|
* Export CF_TEXT to UTF-8.
|
|
*/
|
|
static CFDataRef export_text_to_utf8(HANDLE data)
|
|
{
|
|
return export_codepage_to_utf8(data, CP_ACP);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* export_unicodetext_to_utf8
|
|
*
|
|
* Export CF_UNICODETEXT to UTF-8.
|
|
*/
|
|
static CFDataRef export_unicodetext_to_utf8(HANDLE data)
|
|
{
|
|
CFMutableDataRef ret;
|
|
LPVOID src;
|
|
INT dst_len;
|
|
|
|
src = GlobalLock(data);
|
|
if (!src) return NULL;
|
|
|
|
dst_len = WideCharToMultiByte(CP_UTF8, 0, src, -1, NULL, 0, NULL, NULL);
|
|
if (dst_len) dst_len--; /* Leave off null terminator. */
|
|
ret = CFDataCreateMutable(NULL, dst_len);
|
|
if (ret)
|
|
{
|
|
LPSTR dst;
|
|
int i, j;
|
|
|
|
CFDataSetLength(ret, dst_len);
|
|
dst = (LPSTR)CFDataGetMutableBytePtr(ret);
|
|
WideCharToMultiByte(CP_UTF8, 0, src, -1, dst, dst_len, NULL, NULL);
|
|
|
|
/* Remove carriage returns */
|
|
for (i = 0, j = 0; i < dst_len; i++)
|
|
{
|
|
if (dst[i] == '\r' &&
|
|
(i + 1 >= dst_len || dst[i + 1] == '\n' || dst[i + 1] == '\0'))
|
|
continue;
|
|
dst[j++] = dst[i];
|
|
}
|
|
CFDataSetLength(ret, j);
|
|
}
|
|
GlobalUnlock(data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* get_clipboard_info
|
|
*/
|
|
static BOOL get_clipboard_info(LPCLIPBOARDINFO cbinfo)
|
|
{
|
|
BOOL ret = FALSE;
|
|
|
|
SERVER_START_REQ(set_clipboard_info)
|
|
{
|
|
req->flags = 0;
|
|
|
|
if (wine_server_call_err(req))
|
|
{
|
|
ERR("Failed to get clipboard owner.\n");
|
|
}
|
|
else
|
|
{
|
|
cbinfo->hwnd_owner = wine_server_ptr_handle(reply->old_owner);
|
|
cbinfo->flags = reply->flags;
|
|
|
|
ret = TRUE;
|
|
}
|
|
}
|
|
SERVER_END_REQ;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* release_ownership
|
|
*/
|
|
static BOOL release_ownership(void)
|
|
{
|
|
BOOL ret = FALSE;
|
|
|
|
SERVER_START_REQ(set_clipboard_info)
|
|
{
|
|
req->flags = SET_CB_RELOWNER | SET_CB_SEQNO;
|
|
|
|
if (wine_server_call_err(req))
|
|
ERR("Failed to set clipboard.\n");
|
|
else
|
|
ret = TRUE;
|
|
}
|
|
SERVER_END_REQ;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* check_clipboard_ownership
|
|
*/
|
|
static void check_clipboard_ownership(HWND *owner)
|
|
{
|
|
CLIPBOARDINFO cbinfo;
|
|
|
|
if (owner) *owner = NULL;
|
|
|
|
/* If Wine thinks we're the clipboard owner but Mac OS X thinks we're not
|
|
the pasteboard owner, update Wine. */
|
|
if (get_clipboard_info(&cbinfo) && (cbinfo.flags & CB_PROCESS))
|
|
{
|
|
if (!(cbinfo.flags & CB_OPEN) && !macdrv_is_pasteboard_owner())
|
|
{
|
|
TRACE("Lost clipboard ownership\n");
|
|
|
|
if (OpenClipboard(cbinfo.hwnd_owner))
|
|
{
|
|
/* Destroy private objects */
|
|
SendMessageW(cbinfo.hwnd_owner, WM_DESTROYCLIPBOARD, 0, 0);
|
|
|
|
/* Give up ownership of the windows clipboard */
|
|
release_ownership();
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
else if (owner)
|
|
*owner = cbinfo.hwnd_owner;
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* Mac User Driver Clipboard Exports
|
|
**************************************************************************/
|
|
|
|
|
|
/**************************************************************************
|
|
* AcquireClipboard (MACDRV.@)
|
|
*/
|
|
int CDECL macdrv_AcquireClipboard(HWND hwnd)
|
|
{
|
|
TRACE("hwnd %p\n", hwnd);
|
|
check_clipboard_ownership(NULL);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* CountClipboardFormats (MACDRV.@)
|
|
*/
|
|
INT CDECL macdrv_CountClipboardFormats(void)
|
|
{
|
|
CFMutableSetRef seen_formats;
|
|
CFArrayRef types;
|
|
CFIndex count;
|
|
CFIndex i;
|
|
INT ret = 0;
|
|
|
|
TRACE("()\n");
|
|
check_clipboard_ownership(NULL);
|
|
|
|
seen_formats = CFSetCreateMutable(NULL, 0, NULL);
|
|
if (!seen_formats)
|
|
{
|
|
WARN("Failed to allocate set to track seen formats\n");
|
|
return 0;
|
|
}
|
|
|
|
types = macdrv_copy_pasteboard_types();
|
|
if (!types)
|
|
{
|
|
WARN("Failed to copy pasteboard types\n");
|
|
CFRelease(seen_formats);
|
|
return 0;
|
|
}
|
|
|
|
count = CFArrayGetCount(types);
|
|
TRACE("got %ld types\n", count);
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
CFStringRef type = CFArrayGetValueAtIndex(types, i);
|
|
WINE_CLIPFORMAT* format;
|
|
|
|
format = NULL;
|
|
while ((format = format_for_type(format, type)))
|
|
{
|
|
TRACE("for type %s got format %p/%s\n", debugstr_cf(type), format, debugstr_format(format->format_id));
|
|
|
|
if (!CFSetContainsValue(seen_formats, (void*)format->format_id))
|
|
{
|
|
ret++;
|
|
CFSetAddValue(seen_formats, (void*)format->format_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
CFRelease(seen_formats);
|
|
TRACE(" -> %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* EmptyClipboard (MACDRV.@)
|
|
*
|
|
* Empty cached clipboard data.
|
|
*/
|
|
void CDECL macdrv_EmptyClipboard(BOOL keepunowned)
|
|
{
|
|
TRACE("keepunowned %d\n", keepunowned);
|
|
macdrv_clear_pasteboard();
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* EndClipboardUpdate (MACDRV.@)
|
|
*/
|
|
void CDECL macdrv_EndClipboardUpdate(void)
|
|
{
|
|
TRACE("()\n");
|
|
check_clipboard_ownership(NULL);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* EnumClipboardFormats (MACDRV.@)
|
|
*/
|
|
UINT CDECL macdrv_EnumClipboardFormats(UINT prev_format)
|
|
{
|
|
CFArrayRef types;
|
|
CFIndex count;
|
|
CFIndex i;
|
|
UINT ret;
|
|
|
|
TRACE("prev_format %s\n", debugstr_format(prev_format));
|
|
check_clipboard_ownership(NULL);
|
|
|
|
types = macdrv_copy_pasteboard_types();
|
|
if (!types)
|
|
{
|
|
WARN("Failed to copy pasteboard types\n");
|
|
return 0;
|
|
}
|
|
|
|
count = CFArrayGetCount(types);
|
|
TRACE("got %ld types\n", count);
|
|
|
|
if (!count)
|
|
{
|
|
CFRelease(types);
|
|
return 0;
|
|
}
|
|
|
|
if (prev_format)
|
|
{
|
|
CFMutableArrayRef formats = CFArrayCreateMutable(NULL, 0, NULL);
|
|
if (!formats)
|
|
{
|
|
WARN("Failed to allocate array to track formats\n");
|
|
CFRelease(types);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
CFStringRef type = CFArrayGetValueAtIndex(types, i);
|
|
WINE_CLIPFORMAT* format;
|
|
|
|
format = NULL;
|
|
while ((format = format_for_type(format, type)))
|
|
{
|
|
TRACE("for type %s got format %p/%s\n", debugstr_cf(type), format, debugstr_format(format->format_id));
|
|
|
|
if (format->synthesized)
|
|
{
|
|
/* Don't override a real value with a synthesized value. */
|
|
if (!CFArrayContainsValue(formats, CFRangeMake(0, CFArrayGetCount(formats)), (void*)format->format_id))
|
|
CFArrayAppendValue(formats, (void*)format->format_id);
|
|
}
|
|
else
|
|
{
|
|
/* If the type was already in the array, it must have been synthesized
|
|
because this one's real. Remove the synthesized entry in favor of
|
|
this one. */
|
|
CFIndex index = CFArrayGetFirstIndexOfValue(formats, CFRangeMake(0, CFArrayGetCount(formats)),
|
|
(void*)format->format_id);
|
|
if (index != kCFNotFound)
|
|
CFArrayRemoveValueAtIndex(formats, index);
|
|
CFArrayAppendValue(formats, (void*)format->format_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
count = CFArrayGetCount(formats);
|
|
i = CFArrayGetFirstIndexOfValue(formats, CFRangeMake(0, count), (void*)prev_format);
|
|
if (i == kCFNotFound || i + 1 >= count)
|
|
ret = 0;
|
|
else
|
|
ret = (UINT)CFArrayGetValueAtIndex(formats, i + 1);
|
|
|
|
CFRelease(formats);
|
|
}
|
|
else
|
|
{
|
|
CFStringRef type = CFArrayGetValueAtIndex(types, 0);
|
|
WINE_CLIPFORMAT *format = format_for_type(NULL, type);
|
|
|
|
ret = format ? format->format_id : 0;
|
|
}
|
|
|
|
CFRelease(types);
|
|
TRACE(" -> %u\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* GetClipboardData (MACDRV.@)
|
|
*/
|
|
HANDLE CDECL macdrv_GetClipboardData(UINT desired_format)
|
|
{
|
|
CFArrayRef types;
|
|
CFIndex count;
|
|
CFIndex i;
|
|
CFStringRef type, best_type;
|
|
WINE_CLIPFORMAT* best_format = NULL;
|
|
HANDLE data = NULL;
|
|
|
|
TRACE("desired_format %s\n", debugstr_format(desired_format));
|
|
check_clipboard_ownership(NULL);
|
|
|
|
types = macdrv_copy_pasteboard_types();
|
|
if (!types)
|
|
{
|
|
WARN("Failed to copy pasteboard types\n");
|
|
return NULL;
|
|
}
|
|
|
|
count = CFArrayGetCount(types);
|
|
TRACE("got %ld types\n", count);
|
|
|
|
for (i = 0; (!best_format || best_format->synthesized) && i < count; i++)
|
|
{
|
|
WINE_CLIPFORMAT* format;
|
|
|
|
type = CFArrayGetValueAtIndex(types, i);
|
|
|
|
format = NULL;
|
|
while ((!best_format || best_format->synthesized) && (format = format_for_type(format, type)))
|
|
{
|
|
TRACE("for type %s got format %p/%s\n", debugstr_cf(type), format, debugstr_format(format ? format->format_id : 0));
|
|
|
|
if (format->format_id == desired_format)
|
|
{
|
|
/* The best format is the matching one which is not synthesized. Failing that,
|
|
the best format is the first matching synthesized format. */
|
|
if (!format->synthesized || !best_format)
|
|
{
|
|
best_type = type;
|
|
best_format = format;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best_format)
|
|
{
|
|
CFDataRef pasteboard_data = macdrv_copy_pasteboard_data(best_type);
|
|
|
|
TRACE("got pasteboard data for type %s: %s\n", debugstr_cf(best_type), debugstr_cf(pasteboard_data));
|
|
|
|
if (pasteboard_data)
|
|
{
|
|
data = best_format->import_func(pasteboard_data);
|
|
CFRelease(pasteboard_data);
|
|
}
|
|
}
|
|
|
|
CFRelease(types);
|
|
TRACE(" -> %p\n", data);
|
|
return data;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* IsClipboardFormatAvailable (MACDRV.@)
|
|
*/
|
|
BOOL CDECL macdrv_IsClipboardFormatAvailable(UINT desired_format)
|
|
{
|
|
CFArrayRef types;
|
|
int count;
|
|
UINT i;
|
|
BOOL found = FALSE;
|
|
|
|
TRACE("desired_format %s\n", debugstr_format(desired_format));
|
|
check_clipboard_ownership(NULL);
|
|
|
|
types = macdrv_copy_pasteboard_types();
|
|
if (!types)
|
|
{
|
|
WARN("Failed to copy pasteboard types\n");
|
|
return FALSE;
|
|
}
|
|
|
|
count = CFArrayGetCount(types);
|
|
TRACE("got %d types\n", count);
|
|
|
|
for (i = 0; !found && i < count; i++)
|
|
{
|
|
CFStringRef type = CFArrayGetValueAtIndex(types, i);
|
|
WINE_CLIPFORMAT* format;
|
|
|
|
format = NULL;
|
|
while (!found && (format = format_for_type(format, type)))
|
|
{
|
|
TRACE("for type %s got format %s\n", debugstr_cf(type), debugstr_format(format->format_id));
|
|
|
|
if (format->format_id == desired_format)
|
|
found = TRUE;
|
|
}
|
|
}
|
|
|
|
CFRelease(types);
|
|
TRACE(" -> %d\n", found);
|
|
return found;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* SetClipboardData (MACDRV.@)
|
|
*/
|
|
BOOL CDECL macdrv_SetClipboardData(UINT format_id, HANDLE data, BOOL owner)
|
|
{
|
|
HWND hwnd_owner;
|
|
macdrv_window window;
|
|
WINE_CLIPFORMAT *format;
|
|
CFDataRef cfdata;
|
|
|
|
check_clipboard_ownership(&hwnd_owner);
|
|
window = macdrv_get_cocoa_window(GetAncestor(hwnd_owner, GA_ROOT), FALSE);
|
|
TRACE("format_id %s data %p owner %d hwnd_owner %p window %p)\n", debugstr_format(format_id), data, owner, hwnd_owner, window);
|
|
|
|
if (!data)
|
|
{
|
|
FIXME("delayed rendering (promising) is not implemented yet\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Find the "natural" format for this format_id (the one which isn't
|
|
synthesized from another type). */
|
|
LIST_FOR_EACH_ENTRY(format, &format_list, WINE_CLIPFORMAT, entry)
|
|
if (format->format_id == format_id && !format->synthesized) break;
|
|
|
|
if (&format->entry == &format_list && !(format = insert_clipboard_format(format_id, NULL)))
|
|
{
|
|
WARN("Failed to register clipboard format %s\n", debugstr_format(format_id));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Export the data to the Mac pasteboard. */
|
|
if (!format->export_func || !(cfdata = format->export_func(data)))
|
|
{
|
|
WARN("Failed to export %s data to type %s\n", debugstr_format(format_id), debugstr_cf(format->type));
|
|
return FALSE;
|
|
}
|
|
|
|
if (macdrv_set_pasteboard_data(format->type, cfdata))
|
|
TRACE("Set pasteboard data for type %s: %s\n", debugstr_cf(format->type), debugstr_cf(cfdata));
|
|
else
|
|
{
|
|
WARN("Failed to set pasteboard data for type %s: %s\n", debugstr_cf(format->type), debugstr_cf(cfdata));
|
|
CFRelease(cfdata);
|
|
return FALSE;
|
|
}
|
|
|
|
CFRelease(cfdata);
|
|
|
|
/* Find any other formats for this format_id (the exportable synthesized ones). */
|
|
LIST_FOR_EACH_ENTRY(format, &format_list, WINE_CLIPFORMAT, entry)
|
|
{
|
|
if (format->format_id == format_id && format->synthesized && format->export_func)
|
|
{
|
|
/* We have a synthesized format for this format ID. Add its type to the pasteboard. */
|
|
TRACE("Synthesized from format %s: type %s\n", debugstr_format(format_id), debugstr_cf(format->type));
|
|
|
|
cfdata = format->export_func(data);
|
|
if (!cfdata)
|
|
{
|
|
WARN("Failed to export %s data to type %s\n", debugstr_format(format->format_id), debugstr_cf(format->type));
|
|
continue;
|
|
}
|
|
|
|
if (macdrv_set_pasteboard_data(format->type, cfdata))
|
|
TRACE(" ... set pasteboard data: %s\n", debugstr_cf(cfdata));
|
|
else
|
|
WARN(" ... failed to set pasteboard data: %s\n", debugstr_cf(cfdata));
|
|
|
|
CFRelease(cfdata);
|
|
}
|
|
}
|
|
|
|
/* FIXME: According to MSDN, the caller is entitled to lock and read from
|
|
data until CloseClipboard is called. So, we should defer this cleanup. */
|
|
if ((format_id >= CF_GDIOBJFIRST && format_id <= CF_GDIOBJLAST) ||
|
|
format_id == CF_BITMAP ||
|
|
format_id == CF_DIB ||
|
|
format_id == CF_PALETTE)
|
|
{
|
|
DeleteObject(data);
|
|
}
|
|
else if (format_id == CF_METAFILEPICT)
|
|
{
|
|
DeleteMetaFile(((METAFILEPICT *)GlobalLock(data))->hMF);
|
|
GlobalFree(data);
|
|
}
|
|
else if (format_id == CF_ENHMETAFILE)
|
|
{
|
|
DeleteEnhMetaFile(data);
|
|
}
|
|
else if (format_id < CF_PRIVATEFIRST || CF_PRIVATELAST < format_id)
|
|
{
|
|
GlobalFree(data);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* MACDRV Private Clipboard Exports
|
|
**************************************************************************/
|
|
|
|
|
|
/**************************************************************************
|
|
* macdrv_clipboard_process_attach
|
|
*/
|
|
void macdrv_clipboard_process_attach(void)
|
|
{
|
|
UINT i;
|
|
|
|
/* Register built-in formats */
|
|
for (i = 0; i < sizeof(builtin_format_ids)/sizeof(builtin_format_ids[0]); i++)
|
|
{
|
|
WINE_CLIPFORMAT *format;
|
|
|
|
if (!(format = HeapAlloc(GetProcessHeap(), 0, sizeof(*format)))) break;
|
|
format->format_id = builtin_format_ids[i].id;
|
|
format->type = CFRetain(builtin_format_ids[i].type);
|
|
format->import_func = builtin_format_ids[i].import;
|
|
format->export_func = builtin_format_ids[i].export;
|
|
format->synthesized = builtin_format_ids[i].synthesized;
|
|
list_add_tail(&format_list, &format->entry);
|
|
}
|
|
}
|