From bf824ed38c62f90c0fe66b1b61f7fedc8c377900 Mon Sep 17 00:00:00 2001 From: Ken Thomases Date: Sun, 10 Mar 2013 22:58:21 -0500 Subject: [PATCH] winemac: Implement setting of clipboard data with support for text formats. --- dlls/winemac.drv/clipboard.c | 372 ++++++++++++++++++++++++++++- dlls/winemac.drv/cocoa_clipboard.m | 77 ++++++ dlls/winemac.drv/macdrv_cocoa.h | 3 + dlls/winemac.drv/winemac.drv.spec | 4 + 4 files changed, 444 insertions(+), 12 deletions(-) diff --git a/dlls/winemac.drv/clipboard.c b/dlls/winemac.drv/clipboard.c index 9ae9e9cce12..9a418e51ed4 100644 --- a/dlls/winemac.drv/clipboard.c +++ b/dlls/winemac.drv/clipboard.c @@ -27,6 +27,7 @@ #include "macdrv.h" #include "winuser.h" #include "wine/list.h" +#include "wine/server.h" #include "wine/unicode.h" @@ -37,7 +38,14 @@ 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 { @@ -45,6 +53,7 @@ typedef struct UINT format_id; CFStringRef type; DRVIMPORTFUNC import_func; + DRVEXPORTFUNC export_func; BOOL synthesized; } WINE_CLIPFORMAT; @@ -69,6 +78,11 @@ 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 @@ -123,24 +137,25 @@ 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, FALSE }, - { CF_TEXT, CFSTR("org.winehq.builtin.unicodetext"), import_unicodetext_to_text, TRUE }, - { CF_OEMTEXT, CFSTR("org.winehq.builtin.unicodetext"), import_unicodetext_to_oemtext, TRUE }, + { 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, FALSE }, - { CF_UNICODETEXT, CFSTR("org.winehq.builtin.text"), import_text_to_unicodetext, TRUE }, - { CF_OEMTEXT, CFSTR("org.winehq.builtin.text"), import_text_to_oemtext, 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, FALSE }, - { CF_UNICODETEXT, CFSTR("org.winehq.builtin.oemtext"), import_oemtext_to_unicodetext, TRUE }, - { CF_TEXT, CFSTR("org.winehq.builtin.oemtext"), import_oemtext_to_text, 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, TRUE }, - { CF_TEXT, CFSTR("public.utf8-plain-text"), import_utf8_to_text, TRUE }, - { CF_OEMTEXT, CFSTR("public.utf8-plain-text"), import_utf8_to_oemtext, 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. */ @@ -215,6 +230,7 @@ static WINE_CLIPFORMAT *insert_clipboard_format(UINT id, CFStringRef type) } format->format_id = id; format->import_func = import_clipboard_data; + format->export_func = export_clipboard_data; format->synthesized = FALSE; if (type) @@ -586,11 +602,216 @@ static HANDLE import_utf8_to_unicodetext(CFDataRef data) } +/************************************************************************** + * 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.@) */ @@ -603,6 +824,7 @@ INT CDECL macdrv_CountClipboardFormats(void) INT ret = 0; TRACE("()\n"); + check_clipboard_ownership(NULL); seen_formats = CFSetCreateMutable(NULL, 0, NULL); if (!seen_formats) @@ -646,6 +868,28 @@ INT CDECL macdrv_CountClipboardFormats(void) } +/************************************************************************** + * 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.@) */ @@ -657,6 +901,7 @@ UINT CDECL macdrv_EnumClipboardFormats(UINT prev_format) UINT ret; TRACE("prev_format %s\n", debugstr_format(prev_format)); + check_clipboard_ownership(NULL); types = macdrv_copy_pasteboard_types(); if (!types) @@ -750,6 +995,7 @@ HANDLE CDECL macdrv_GetClipboardData(UINT desired_format) HANDLE data = NULL; TRACE("desired_format %s\n", debugstr_format(desired_format)); + check_clipboard_ownership(NULL); types = macdrv_copy_pasteboard_types(); if (!types) @@ -815,6 +1061,7 @@ BOOL CDECL macdrv_IsClipboardFormatAvailable(UINT desired_format) BOOL found = FALSE; TRACE("desired_format %s\n", debugstr_format(desired_format)); + check_clipboard_ownership(NULL); types = macdrv_copy_pasteboard_types(); if (!types) @@ -847,6 +1094,106 @@ BOOL CDECL macdrv_IsClipboardFormatAvailable(UINT desired_format) } +/************************************************************************** + * 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 **************************************************************************/ @@ -868,6 +1215,7 @@ void macdrv_clipboard_process_attach(void) 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); } diff --git a/dlls/winemac.drv/cocoa_clipboard.m b/dlls/winemac.drv/cocoa_clipboard.m index 753a4973e47..e908eab286b 100644 --- a/dlls/winemac.drv/cocoa_clipboard.m +++ b/dlls/winemac.drv/cocoa_clipboard.m @@ -22,6 +22,25 @@ #import "cocoa_app.h" +static int owned_change_count = -1; + + +/*********************************************************************** + * macdrv_is_pasteboard_owner + */ +int macdrv_is_pasteboard_owner(void) +{ + __block int ret; + + OnMainThread(^{ + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + ret = ([pb changeCount] == owned_change_count); + }); + + return ret; +} + + /*********************************************************************** * macdrv_copy_pasteboard_types * @@ -78,3 +97,61 @@ CFDataRef macdrv_copy_pasteboard_data(CFStringRef type) return (CFDataRef)ret; } + + +/*********************************************************************** + * macdrv_clear_pasteboard + * + * Takes ownership of the Mac pasteboard and clears it of all data types. + */ +void macdrv_clear_pasteboard(void) +{ + OnMainThreadAsync(^{ + @try + { + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + owned_change_count = [pb declareTypes:[NSArray array] owner:nil]; + } + @catch (id e) + { + ERR(@"Exception discarded while clearing pasteboard: %@\n", e); + } + }); +} + + +/*********************************************************************** + * macdrv_set_pasteboard_data + * + * Sets the pasteboard data for a specified type. Replaces any data of + * that type already on the pasteboard. + * + * Returns 0 on error, non-zero on success. + */ +int macdrv_set_pasteboard_data(CFStringRef type, CFDataRef data) +{ + __block int ret = 0; + + OnMainThread(^{ + @try + { + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + NSInteger change_count = [pb addTypes:[NSArray arrayWithObject:(NSString*)type] + owner:nil]; + if (change_count) + { + owned_change_count = change_count; + if (data) + ret = [pb setData:(NSData*)data forType:(NSString*)type]; + else + ret = 1; + } + } + @catch (id e) + { + ERR(@"Exception discarded while copying pasteboard types: %@\n", e); + } + }); + + return ret; +} diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index e36c3011082..e7cd9f82081 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -276,6 +276,9 @@ extern void macdrv_set_window_color_key(macdrv_window w, CGFloat keyRed, CGFloat /* clipboard */ extern CFArrayRef macdrv_copy_pasteboard_types(void) DECLSPEC_HIDDEN; extern CFDataRef macdrv_copy_pasteboard_data(CFStringRef type) DECLSPEC_HIDDEN; +extern int macdrv_is_pasteboard_owner(void) DECLSPEC_HIDDEN; +extern void macdrv_clear_pasteboard(void) DECLSPEC_HIDDEN; +extern int macdrv_set_pasteboard_data(CFStringRef type, CFDataRef data) DECLSPEC_HIDDEN; /* opengl */ diff --git a/dlls/winemac.drv/winemac.drv.spec b/dlls/winemac.drv/winemac.drv.spec index d1195f185f8..8bf3691a585 100644 --- a/dlls/winemac.drv/winemac.drv.spec +++ b/dlls/winemac.drv/winemac.drv.spec @@ -4,6 +4,7 @@ # USER driver +@ cdecl AcquireClipboard(long) macdrv_AcquireClipboard @ cdecl ActivateKeyboardLayout(long long) macdrv_ActivateKeyboardLayout @ cdecl Beep() macdrv_Beep @ cdecl ChangeDisplaySettingsEx(ptr ptr long long long) macdrv_ChangeDisplaySettingsEx @@ -13,6 +14,8 @@ @ cdecl CreateWindow(long) macdrv_CreateWindow @ cdecl DestroyCursorIcon(long) macdrv_DestroyCursorIcon @ cdecl DestroyWindow(long) macdrv_DestroyWindow +@ cdecl EmptyClipboard(long) macdrv_EmptyClipboard +@ cdecl EndClipboardUpdate() macdrv_EndClipboardUpdate @ cdecl EnumClipboardFormats(long) macdrv_EnumClipboardFormats @ cdecl EnumDisplayMonitors(long ptr ptr long) macdrv_EnumDisplayMonitors @ cdecl EnumDisplaySettingsEx(ptr long ptr long) macdrv_EnumDisplaySettingsEx @@ -26,6 +29,7 @@ @ cdecl MapVirtualKeyEx(long long long) macdrv_MapVirtualKeyEx @ cdecl MsgWaitForMultipleObjectsEx(long ptr long long long) macdrv_MsgWaitForMultipleObjectsEx @ cdecl ScrollDC(long long long ptr ptr long ptr) macdrv_ScrollDC +@ cdecl SetClipboardData(long long long) macdrv_SetClipboardData @ cdecl SetCursor(long) macdrv_SetCursor @ cdecl SetCursorPos(long long) macdrv_SetCursorPos @ cdecl SetFocus(long) macdrv_SetFocus