| /* |
| * MACDRV mouse driver |
| * |
| * Copyright 1998 Ulrich Weigand |
| * Copyright 2007 Henri Verbeet |
| * 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" |
| #define OEMRESOURCE |
| #include "winuser.h" |
| #include "winreg.h" |
| #include "wine/server.h" |
| #include "wine/unicode.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(cursor); |
| |
| |
| static CRITICAL_SECTION cursor_cache_section; |
| static CRITICAL_SECTION_DEBUG critsect_debug = |
| { |
| 0, 0, &cursor_cache_section, |
| { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, |
| 0, 0, { (DWORD_PTR)(__FILE__ ": cursor_cache_section") } |
| }; |
| static CRITICAL_SECTION cursor_cache_section = { &critsect_debug, -1, 0, 0, 0, 0 }; |
| |
| static CFMutableDictionaryRef cursor_cache; |
| |
| |
| struct system_cursors |
| { |
| WORD id; |
| CFStringRef name; |
| }; |
| |
| static const struct system_cursors user32_cursors[] = |
| { |
| { OCR_NORMAL, CFSTR("arrowCursor") }, |
| { OCR_IBEAM, CFSTR("IBeamCursor") }, |
| { OCR_CROSS, CFSTR("crosshairCursor") }, |
| { OCR_SIZEWE, CFSTR("resizeLeftRightCursor") }, |
| { OCR_SIZENS, CFSTR("resizeUpDownCursor") }, |
| { OCR_NO, CFSTR("operationNotAllowedCursor") }, |
| { OCR_HAND, CFSTR("pointingHandCursor") }, |
| { 0 } |
| }; |
| |
| static const struct system_cursors comctl32_cursors[] = |
| { |
| { 102, CFSTR("closedHandCursor") }, |
| { 104, CFSTR("dragCopyCursor") }, |
| { 105, CFSTR("arrowCursor") }, |
| { 106, CFSTR("resizeLeftRightCursor") }, |
| { 107, CFSTR("resizeLeftRightCursor") }, |
| { 108, CFSTR("pointingHandCursor") }, |
| { 135, CFSTR("resizeUpDownCursor") }, |
| { 0 } |
| }; |
| |
| static const struct system_cursors ole32_cursors[] = |
| { |
| { 1, CFSTR("operationNotAllowedCursor") }, |
| { 2, CFSTR("closedHandCursor") }, |
| { 3, CFSTR("dragCopyCursor") }, |
| { 4, CFSTR("dragLinkCursor") }, |
| { 0 } |
| }; |
| |
| static const struct system_cursors riched20_cursors[] = |
| { |
| { 105, CFSTR("pointingHandCursor") }, |
| { 109, CFSTR("dragCopyCursor") }, |
| { 110, CFSTR("closedHandCursor") }, |
| { 111, CFSTR("operationNotAllowedCursor") }, |
| { 0 } |
| }; |
| |
| static const struct |
| { |
| const struct system_cursors *cursors; |
| WCHAR name[16]; |
| } module_cursors[] = |
| { |
| { user32_cursors, {'u','s','e','r','3','2','.','d','l','l',0} }, |
| { comctl32_cursors, {'c','o','m','c','t','l','3','2','.','d','l','l',0} }, |
| { ole32_cursors, {'o','l','e','3','2','.','d','l','l',0} }, |
| { riched20_cursors, {'r','i','c','h','e','d','2','0','.','d','l','l',0} } |
| }; |
| |
| /* The names of NSCursor class methods which return cursor objects. */ |
| static const CFStringRef cocoa_cursor_names[] = |
| { |
| CFSTR("arrowCursor"), |
| CFSTR("closedHandCursor"), |
| CFSTR("contextualMenuCursor"), |
| CFSTR("crosshairCursor"), |
| CFSTR("disappearingItemCursor"), |
| CFSTR("dragCopyCursor"), |
| CFSTR("dragLinkCursor"), |
| CFSTR("IBeamCursor"), |
| CFSTR("IBeamCursorForVerticalLayout"), |
| CFSTR("openHandCursor"), |
| CFSTR("operationNotAllowedCursor"), |
| CFSTR("pointingHandCursor"), |
| CFSTR("resizeDownCursor"), |
| CFSTR("resizeLeftCursor"), |
| CFSTR("resizeLeftRightCursor"), |
| CFSTR("resizeRightCursor"), |
| CFSTR("resizeUpCursor"), |
| CFSTR("resizeUpDownCursor"), |
| }; |
| |
| |
| /*********************************************************************** |
| * send_mouse_input |
| * |
| * Update the various window states on a mouse event. |
| */ |
| static void send_mouse_input(HWND hwnd, macdrv_window cocoa_window, UINT flags, int x, int y, |
| DWORD mouse_data, BOOL drag, unsigned long time) |
| { |
| INPUT input; |
| HWND top_level_hwnd; |
| |
| top_level_hwnd = GetAncestor(hwnd, GA_ROOT); |
| |
| if ((flags & MOUSEEVENTF_MOVE) && (flags & MOUSEEVENTF_ABSOLUTE) && !drag && |
| cocoa_window != macdrv_thread_data()->capture_window) |
| { |
| RECT rect; |
| |
| /* update the wine server Z-order */ |
| SetRect(&rect, x, y, x + 1, y + 1); |
| MapWindowPoints(0, top_level_hwnd, (POINT *)&rect, 2); |
| |
| SERVER_START_REQ(update_window_zorder) |
| { |
| req->window = wine_server_user_handle(top_level_hwnd); |
| req->rect.left = rect.left; |
| req->rect.top = rect.top; |
| req->rect.right = rect.right; |
| req->rect.bottom = rect.bottom; |
| wine_server_call(req); |
| } |
| SERVER_END_REQ; |
| } |
| |
| input.type = INPUT_MOUSE; |
| input.mi.dx = x; |
| input.mi.dy = y; |
| input.mi.mouseData = mouse_data; |
| input.mi.dwFlags = flags; |
| input.mi.time = time; |
| input.mi.dwExtraInfo = 0; |
| |
| __wine_send_input(top_level_hwnd, &input); |
| } |
| |
| |
| /*********************************************************************** |
| * copy_system_cursor_name |
| */ |
| CFStringRef copy_system_cursor_name(ICONINFOEXW *info) |
| { |
| static const WCHAR idW[] = {'%','h','u',0}; |
| const struct system_cursors *cursors; |
| unsigned int i; |
| CFStringRef cursor_name = NULL; |
| HMODULE module; |
| HKEY key; |
| WCHAR *p, name[MAX_PATH * 2]; |
| |
| TRACE("info->szModName %s info->szResName %s info->wResID %hu\n", debugstr_w(info->szModName), |
| debugstr_w(info->szResName), info->wResID); |
| |
| if (!info->szModName[0]) return NULL; |
| |
| p = strrchrW(info->szModName, '\\'); |
| strcpyW(name, p ? p + 1 : info->szModName); |
| p = name + strlenW(name); |
| *p++ = ','; |
| if (info->szResName[0]) strcpyW(p, info->szResName); |
| else sprintfW(p, idW, info->wResID); |
| |
| /* @@ Wine registry key: HKCU\Software\Wine\Mac Driver\Cursors */ |
| if (!RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\Mac Driver\\Cursors", &key)) |
| { |
| WCHAR value[64]; |
| DWORD size, ret; |
| |
| value[0] = 0; |
| size = sizeof(value); |
| ret = RegQueryValueExW(key, name, NULL, NULL, (BYTE *)value, &size); |
| RegCloseKey(key); |
| if (!ret) |
| { |
| if (!value[0]) |
| { |
| TRACE("registry forces standard cursor for %s\n", debugstr_w(name)); |
| return NULL; /* force standard cursor */ |
| } |
| |
| cursor_name = CFStringCreateWithCharacters(NULL, value, strlenW(value)); |
| if (!cursor_name) |
| { |
| WARN("CFStringCreateWithCharacters failed for %s\n", debugstr_w(value)); |
| return NULL; |
| } |
| |
| /* Make sure it's one of the appropriate NSCursor class methods. */ |
| for (i = 0; i < sizeof(cocoa_cursor_names) / sizeof(cocoa_cursor_names[0]); i++) |
| if (CFEqual(cursor_name, cocoa_cursor_names[i])) |
| goto done; |
| |
| WARN("%s mapped to invalid Cocoa cursor name %s\n", debugstr_w(name), debugstr_w(value)); |
| CFRelease(cursor_name); |
| return NULL; |
| } |
| } |
| |
| if (info->szResName[0]) goto done; /* only integer resources are supported here */ |
| if (!(module = GetModuleHandleW(info->szModName))) goto done; |
| |
| for (i = 0; i < sizeof(module_cursors)/sizeof(module_cursors[0]); i++) |
| if (GetModuleHandleW(module_cursors[i].name) == module) break; |
| if (i == sizeof(module_cursors)/sizeof(module_cursors[0])) goto done; |
| |
| cursors = module_cursors[i].cursors; |
| for (i = 0; cursors[i].id; i++) |
| if (cursors[i].id == info->wResID) |
| { |
| cursor_name = CFRetain(cursors[i].name); |
| break; |
| } |
| |
| done: |
| if (cursor_name) |
| TRACE("%s -> %s\n", debugstr_w(name), debugstr_cf(cursor_name)); |
| else |
| WARN("no system cursor found for %s\n", debugstr_w(name)); |
| return cursor_name; |
| } |
| |
| /*********************************************************************** |
| * create_monochrome_cursor |
| */ |
| CFArrayRef create_monochrome_cursor(HDC hdc, const ICONINFOEXW *icon, int width, int height) |
| { |
| char buffer[FIELD_OFFSET(BITMAPINFO, bmiColors[256])]; |
| BITMAPINFO *info = (BITMAPINFO *)buffer; |
| unsigned int width_bytes = (width + 31) / 32 * 4; |
| unsigned long *and_bits = NULL, *xor_bits; |
| unsigned long *data_bits; |
| int count, i; |
| CGColorSpaceRef colorspace; |
| CFMutableDataRef data; |
| CGDataProviderRef provider; |
| CGImageRef cgimage, cgmask, cgmasked; |
| CGPoint hot_spot; |
| CFDictionaryRef hot_spot_dict; |
| const CFStringRef keys[] = { CFSTR("image"), CFSTR("hotSpot") }; |
| CFTypeRef values[sizeof(keys) / sizeof(keys[0])]; |
| CFDictionaryRef frame; |
| CFArrayRef frames; |
| |
| TRACE("hdc %p icon->hbmMask %p icon->xHotspot %d icon->yHotspot %d width %d height %d\n", |
| hdc, icon->hbmMask, icon->xHotspot, icon->yHotspot, width, height); |
| |
| info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); |
| info->bmiHeader.biWidth = width; |
| info->bmiHeader.biHeight = -height * 2; |
| info->bmiHeader.biPlanes = 1; |
| info->bmiHeader.biBitCount = 1; |
| info->bmiHeader.biCompression = BI_RGB; |
| info->bmiHeader.biSizeImage = width_bytes * height * 2; |
| info->bmiHeader.biXPelsPerMeter = 0; |
| info->bmiHeader.biYPelsPerMeter = 0; |
| info->bmiHeader.biClrUsed = 0; |
| info->bmiHeader.biClrImportant = 0; |
| |
| and_bits = HeapAlloc(GetProcessHeap(), 0, info->bmiHeader.biSizeImage); |
| if (!and_bits) |
| { |
| WARN("failed to allocate and_bits\n"); |
| return NULL; |
| } |
| xor_bits = (unsigned long*)((char*)and_bits + info->bmiHeader.biSizeImage / 2); |
| |
| if (!GetDIBits(hdc, icon->hbmMask, 0, height * 2, and_bits, info, DIB_RGB_COLORS)) |
| { |
| WARN("GetDIBits failed\n"); |
| HeapFree(GetProcessHeap(), 0, and_bits); |
| return NULL; |
| } |
| |
| /* On Windows, the pixels of a monochrome cursor can have four effects: |
| draw black, draw white, leave unchanged (transparent), or invert. The Mac |
| only supports the first three. It can't do pixels which invert the |
| background. Since the background is usually white, I am arbitrarily |
| mapping "invert" to "draw black". This entails bitwise math between the |
| cursor's AND mask and XOR mask: |
| |
| AND | XOR | Windows cursor pixel |
| -------------------------------- |
| 0 | 0 | black |
| 0 | 1 | white |
| 1 | 0 | transparent |
| 1 | 1 | invert |
| |
| AND | XOR | Mac image |
| --------------------- |
| 0 | 0 | black (0) |
| 0 | 1 | white (1) |
| 1 | 0 | don't care |
| 1 | 1 | black (0) |
| |
| AND | XOR | Mac mask |
| --------------------------- |
| 0 | 0 | paint (0) |
| 0 | 1 | paint (0) |
| 1 | 0 | don't paint (1) |
| 1 | 1 | paint (0) |
| |
| So, Mac image = AND ^ XOR and Mac mask = AND & ~XOR. |
| */ |
| /* Create data for Mac image. */ |
| data = CFDataCreateMutable(NULL, info->bmiHeader.biSizeImage / 2); |
| if (!data) |
| { |
| WARN("failed to create data\n"); |
| HeapFree(GetProcessHeap(), 0, and_bits); |
| return NULL; |
| } |
| |
| /* image data = AND mask */ |
| CFDataAppendBytes(data, (UInt8*)and_bits, info->bmiHeader.biSizeImage / 2); |
| /* image data ^= XOR mask */ |
| data_bits = (unsigned long*)CFDataGetMutableBytePtr(data); |
| count = (info->bmiHeader.biSizeImage / 2) / sizeof(*data_bits); |
| for (i = 0; i < count; i++) |
| data_bits[i] ^= xor_bits[i]; |
| |
| colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray); |
| if (!colorspace) |
| { |
| WARN("failed to create colorspace\n"); |
| CFRelease(data); |
| HeapFree(GetProcessHeap(), 0, and_bits); |
| return NULL; |
| } |
| |
| provider = CGDataProviderCreateWithCFData(data); |
| CFRelease(data); |
| if (!provider) |
| { |
| WARN("failed to create data provider\n"); |
| CGColorSpaceRelease(colorspace); |
| HeapFree(GetProcessHeap(), 0, and_bits); |
| return NULL; |
| } |
| |
| cgimage = CGImageCreate(width, height, 1, 1, width_bytes, colorspace, |
| kCGImageAlphaNone | kCGBitmapByteOrderDefault, |
| provider, NULL, FALSE, kCGRenderingIntentDefault); |
| CGDataProviderRelease(provider); |
| CGColorSpaceRelease(colorspace); |
| if (!cgimage) |
| { |
| WARN("failed to create image\n"); |
| HeapFree(GetProcessHeap(), 0, and_bits); |
| return NULL; |
| } |
| |
| /* Create data for mask. */ |
| data = CFDataCreateMutable(NULL, info->bmiHeader.biSizeImage / 2); |
| if (!data) |
| { |
| WARN("failed to create data\n"); |
| CGImageRelease(cgimage); |
| HeapFree(GetProcessHeap(), 0, and_bits); |
| return NULL; |
| } |
| |
| /* mask data = AND mask */ |
| CFDataAppendBytes(data, (UInt8*)and_bits, info->bmiHeader.biSizeImage / 2); |
| /* mask data &= ~XOR mask */ |
| data_bits = (unsigned long*)CFDataGetMutableBytePtr(data); |
| for (i = 0; i < count; i++) |
| data_bits[i] &= ~xor_bits[i]; |
| HeapFree(GetProcessHeap(), 0, and_bits); |
| |
| provider = CGDataProviderCreateWithCFData(data); |
| CFRelease(data); |
| if (!provider) |
| { |
| WARN("failed to create data provider\n"); |
| CGImageRelease(cgimage); |
| return NULL; |
| } |
| |
| cgmask = CGImageMaskCreate(width, height, 1, 1, width_bytes, provider, NULL, FALSE); |
| CGDataProviderRelease(provider); |
| if (!cgmask) |
| { |
| WARN("failed to create mask image\n"); |
| CGImageRelease(cgimage); |
| return NULL; |
| } |
| |
| cgmasked = CGImageCreateWithMask(cgimage, cgmask); |
| CGImageRelease(cgimage); |
| CGImageRelease(cgmask); |
| if (!cgmasked) |
| { |
| WARN("failed to create masked image\n"); |
| return NULL; |
| } |
| |
| hot_spot = CGPointMake(icon->xHotspot, icon->yHotspot); |
| hot_spot_dict = CGPointCreateDictionaryRepresentation(hot_spot); |
| if (!hot_spot_dict) |
| { |
| WARN("failed to create hot spot dictionary\n"); |
| CGImageRelease(cgmasked); |
| return NULL; |
| } |
| |
| values[0] = cgmasked; |
| values[1] = hot_spot_dict; |
| frame = CFDictionaryCreate(NULL, (const void**)keys, values, sizeof(keys) / sizeof(keys[0]), |
| &kCFCopyStringDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| CFRelease(hot_spot_dict); |
| CGImageRelease(cgmasked); |
| if (!frame) |
| { |
| WARN("failed to create frame dictionary\n"); |
| return NULL; |
| } |
| |
| frames = CFArrayCreate(NULL, (const void**)&frame, 1, &kCFTypeArrayCallBacks); |
| CFRelease(frame); |
| if (!frames) |
| { |
| WARN("failed to create frames array\n"); |
| return NULL; |
| } |
| |
| return frames; |
| } |
| |
| |
| /*********************************************************************** |
| * create_cursor_frame |
| * |
| * Create a frame dictionary for a cursor from a Windows icon. |
| * Keys: |
| * "image" a CGImage for the frame |
| * "duration" a CFNumber for the frame duration in seconds |
| * "hotSpot" a CFDictionary encoding a CGPoint for the hot spot |
| */ |
| static CFDictionaryRef create_cursor_frame(HDC hdc, const ICONINFOEXW *iinfo, HANDLE icon, |
| HBITMAP hbmColor, unsigned char *color_bits, int color_size, |
| HBITMAP hbmMask, unsigned char *mask_bits, int mask_size, |
| int width, int height, int istep) |
| { |
| DWORD delay_jiffies, num_steps; |
| CFMutableDictionaryRef frame; |
| CGPoint hot_spot; |
| CFDictionaryRef hot_spot_dict; |
| double duration; |
| CFNumberRef duration_number; |
| CGImageRef cgimage; |
| |
| TRACE("hdc %p iinfo->xHotspot %d iinfo->yHotspot %d icon %p hbmColor %p color_bits %p color_size %d" |
| " hbmMask %p mask_bits %p mask_size %d width %d height %d istep %d\n", |
| hdc, iinfo->xHotspot, iinfo->yHotspot, icon, hbmColor, color_bits, color_size, |
| hbmMask, mask_bits, mask_size, width, height, istep); |
| |
| frame = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks); |
| if (!frame) |
| { |
| WARN("failed to allocate dictionary for frame\n"); |
| return NULL; |
| } |
| |
| hot_spot = CGPointMake(iinfo->xHotspot, iinfo->yHotspot); |
| hot_spot_dict = CGPointCreateDictionaryRepresentation(hot_spot); |
| if (!hot_spot_dict) |
| { |
| WARN("failed to create hot spot dictionary\n"); |
| CFRelease(frame); |
| return NULL; |
| } |
| CFDictionarySetValue(frame, CFSTR("hotSpot"), hot_spot_dict); |
| CFRelease(hot_spot_dict); |
| |
| if (GetCursorFrameInfo(icon, 0x0 /* unknown parameter */, istep, &delay_jiffies, &num_steps) != 0) |
| duration = delay_jiffies / 60.0; /* convert jiffies (1/60s) to seconds */ |
| else |
| { |
| WARN("Failed to retrieve animated cursor frame-rate for frame %d.\n", istep); |
| duration = 0.1; /* fallback delay, 100 ms */ |
| } |
| duration_number = CFNumberCreate(NULL, kCFNumberDoubleType, &duration); |
| if (!duration_number) |
| { |
| WARN("failed to create duration number\n"); |
| CFRelease(frame); |
| return NULL; |
| } |
| CFDictionarySetValue(frame, CFSTR("duration"), duration_number); |
| CFRelease(duration_number); |
| |
| cgimage = create_cgimage_from_icon_bitmaps(hdc, icon, hbmColor, color_bits, color_size, |
| hbmMask, mask_bits, mask_size, width, height, istep); |
| if (!cgimage) |
| { |
| CFRelease(frame); |
| return NULL; |
| } |
| |
| CFDictionarySetValue(frame, CFSTR("image"), cgimage); |
| CGImageRelease(cgimage); |
| |
| return frame; |
| } |
| |
| |
| /*********************************************************************** |
| * create_color_cursor |
| * |
| * Create an array of color cursor frames from a Windows cursor. Each |
| * frame is represented in the array by a dictionary. |
| * Frame dictionary keys: |
| * "image" a CGImage for the frame |
| * "duration" a CFNumber for the frame duration in seconds |
| * "hotSpot" a CFDictionary encoding a CGPoint for the hot spot |
| */ |
| static CFArrayRef create_color_cursor(HDC hdc, const ICONINFOEXW *iinfo, HANDLE icon, int width, int height) |
| { |
| unsigned char *color_bits, *mask_bits; |
| HBITMAP hbmColor = 0, hbmMask = 0; |
| DWORD nFrames, delay_jiffies, i; |
| int color_size, mask_size; |
| BITMAPINFO *info = NULL; |
| CFMutableArrayRef frames; |
| |
| TRACE("hdc %p iinfo %p icon %p width %d height %d\n", hdc, iinfo, icon, width, height); |
| |
| /* Retrieve the number of frames to render */ |
| if (!GetCursorFrameInfo(icon, 0x0 /* unknown parameter */, 0, &delay_jiffies, &nFrames)) |
| { |
| WARN("GetCursorFrameInfo failed\n"); |
| return NULL; |
| } |
| if (!(frames = CFArrayCreateMutable(NULL, nFrames, &kCFTypeArrayCallBacks))) |
| { |
| WARN("failed to allocate frames array\n"); |
| return NULL; |
| } |
| |
| /* Allocate all of the resources necessary to obtain a cursor frame */ |
| if (!(info = HeapAlloc(GetProcessHeap(), 0, FIELD_OFFSET(BITMAPINFO, bmiColors[256])))) goto cleanup; |
| info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); |
| info->bmiHeader.biWidth = width; |
| info->bmiHeader.biHeight = -height; |
| info->bmiHeader.biPlanes = 1; |
| info->bmiHeader.biCompression = BI_RGB; |
| info->bmiHeader.biXPelsPerMeter = 0; |
| info->bmiHeader.biYPelsPerMeter = 0; |
| info->bmiHeader.biClrUsed = 0; |
| info->bmiHeader.biClrImportant = 0; |
| info->bmiHeader.biBitCount = 32; |
| color_size = width * height * 4; |
| info->bmiHeader.biSizeImage = color_size; |
| hbmColor = CreateDIBSection(hdc, info, DIB_RGB_COLORS, (VOID **) &color_bits, NULL, 0); |
| if (!hbmColor) |
| { |
| WARN("failed to create DIB section for cursor color data\n"); |
| goto cleanup; |
| } |
| info->bmiHeader.biBitCount = 1; |
| info->bmiColors[0].rgbRed = 0; |
| info->bmiColors[0].rgbGreen = 0; |
| info->bmiColors[0].rgbBlue = 0; |
| info->bmiColors[0].rgbReserved = 0; |
| info->bmiColors[1].rgbRed = 0xff; |
| info->bmiColors[1].rgbGreen = 0xff; |
| info->bmiColors[1].rgbBlue = 0xff; |
| info->bmiColors[1].rgbReserved = 0; |
| |
| mask_size = ((width + 31) / 32 * 4) * height; /* width_bytes * height */ |
| info->bmiHeader.biSizeImage = mask_size; |
| hbmMask = CreateDIBSection(hdc, info, DIB_RGB_COLORS, (VOID **) &mask_bits, NULL, 0); |
| if (!hbmMask) |
| { |
| WARN("failed to create DIB section for cursor mask data\n"); |
| goto cleanup; |
| } |
| |
| /* Create a CFDictionary for each frame of the cursor */ |
| for (i = 0; i < nFrames; i++) |
| { |
| CFDictionaryRef frame = create_cursor_frame(hdc, iinfo, icon, |
| hbmColor, color_bits, color_size, |
| hbmMask, mask_bits, mask_size, |
| width, height, i); |
| if (!frame) goto cleanup; |
| CFArrayAppendValue(frames, frame); |
| CFRelease(frame); |
| } |
| |
| cleanup: |
| if (CFArrayGetCount(frames) < nFrames) |
| { |
| CFRelease(frames); |
| frames = NULL; |
| } |
| else |
| TRACE("returning cursor with %d frames\n", nFrames); |
| /* Cleanup all of the resources used to obtain the frame data */ |
| if (hbmColor) DeleteObject(hbmColor); |
| if (hbmMask) DeleteObject(hbmMask); |
| HeapFree(GetProcessHeap(), 0, info); |
| return frames; |
| } |
| |
| |
| /*********************************************************************** |
| * DestroyCursorIcon (MACDRV.@) |
| */ |
| void CDECL macdrv_DestroyCursorIcon(HCURSOR cursor) |
| { |
| TRACE("cursor %p\n", cursor); |
| |
| EnterCriticalSection(&cursor_cache_section); |
| if (cursor_cache) |
| CFDictionaryRemoveValue(cursor_cache, cursor); |
| LeaveCriticalSection(&cursor_cache_section); |
| } |
| |
| |
| /*********************************************************************** |
| * ClipCursor (MACDRV.@) |
| * |
| * Set the cursor clipping rectangle. |
| */ |
| BOOL CDECL macdrv_ClipCursor(LPCRECT clip) |
| { |
| CGRect rect; |
| |
| TRACE("%s\n", wine_dbgstr_rect(clip)); |
| |
| if (clip) |
| { |
| rect = CGRectMake(clip->left, clip->top, max(1, clip->right - clip->left), |
| max(1, clip->bottom - clip->top)); |
| } |
| else |
| rect = CGRectInfinite; |
| |
| /* FIXME: This needs to be done not just in this process but in all of the |
| ones for this WINEPREFIX. Broadcast a message to do that. */ |
| |
| return macdrv_clip_cursor(rect); |
| } |
| |
| |
| /*********************************************************************** |
| * GetCursorPos (MACDRV.@) |
| */ |
| BOOL CDECL macdrv_GetCursorPos(LPPOINT pos) |
| { |
| CGPoint pt; |
| BOOL ret; |
| |
| ret = macdrv_get_cursor_position(&pt); |
| if (ret) |
| { |
| TRACE("pointer at (%g,%g) server pos %d,%d\n", pt.x, pt.y, pos->x, pos->y); |
| pos->x = floor(pt.x); |
| pos->y = floor(pt.y); |
| } |
| return ret; |
| } |
| |
| |
| /*********************************************************************** |
| * SetCapture (MACDRV.@) |
| */ |
| void CDECL macdrv_SetCapture(HWND hwnd, UINT flags) |
| { |
| struct macdrv_thread_data *thread_data = macdrv_thread_data(); |
| HWND top = GetAncestor(hwnd, GA_ROOT); |
| macdrv_window cocoa_window = macdrv_get_cocoa_window(top, FALSE); |
| |
| TRACE("hwnd %p top %p/%p flags 0x%08x\n", hwnd, top, cocoa_window, flags); |
| |
| if (!thread_data) return; |
| |
| thread_data->capture_window = cocoa_window; |
| macdrv_set_mouse_capture_window(cocoa_window); |
| } |
| |
| |
| /*********************************************************************** |
| * SetCursor (MACDRV.@) |
| */ |
| void CDECL macdrv_SetCursor(HCURSOR cursor) |
| { |
| CFStringRef cursor_name = NULL; |
| CFArrayRef cursor_frames = NULL; |
| |
| TRACE("%p\n", cursor); |
| |
| if (cursor) |
| { |
| ICONINFOEXW info; |
| |
| EnterCriticalSection(&cursor_cache_section); |
| if (cursor_cache) |
| { |
| CFTypeRef cached_cursor = CFDictionaryGetValue(cursor_cache, cursor); |
| if (cached_cursor) |
| { |
| if (CFGetTypeID(cached_cursor) == CFStringGetTypeID()) |
| cursor_name = CFRetain(cached_cursor); |
| else |
| cursor_frames = CFRetain(cached_cursor); |
| } |
| } |
| LeaveCriticalSection(&cursor_cache_section); |
| if (cursor_name || cursor_frames) |
| goto done; |
| |
| info.cbSize = sizeof(info); |
| if (!GetIconInfoExW(cursor, &info)) |
| { |
| WARN("GetIconInfoExW failed\n"); |
| return; |
| } |
| |
| if ((cursor_name = copy_system_cursor_name(&info))) |
| { |
| DeleteObject(info.hbmColor); |
| DeleteObject(info.hbmMask); |
| } |
| else |
| { |
| BITMAP bm; |
| HDC hdc; |
| |
| GetObjectW(info.hbmMask, sizeof(bm), &bm); |
| if (!info.hbmColor) bm.bmHeight = max(1, bm.bmHeight / 2); |
| |
| /* make sure hotspot is valid */ |
| if (info.xHotspot >= bm.bmWidth || info.yHotspot >= bm.bmHeight) |
| { |
| info.xHotspot = bm.bmWidth / 2; |
| info.yHotspot = bm.bmHeight / 2; |
| } |
| |
| hdc = CreateCompatibleDC(0); |
| |
| if (info.hbmColor) |
| { |
| cursor_frames = create_color_cursor(hdc, &info, cursor, bm.bmWidth, bm.bmHeight); |
| DeleteObject(info.hbmColor); |
| } |
| else |
| cursor_frames = create_monochrome_cursor(hdc, &info, bm.bmWidth, bm.bmHeight); |
| |
| DeleteObject(info.hbmMask); |
| DeleteDC(hdc); |
| } |
| |
| if (cursor_name || cursor_frames) |
| { |
| EnterCriticalSection(&cursor_cache_section); |
| if (!cursor_cache) |
| cursor_cache = CFDictionaryCreateMutable(NULL, 0, NULL, |
| &kCFTypeDictionaryValueCallBacks); |
| CFDictionarySetValue(cursor_cache, cursor, |
| cursor_name ? (CFTypeRef)cursor_name : (CFTypeRef)cursor_frames); |
| LeaveCriticalSection(&cursor_cache_section); |
| } |
| else |
| cursor_name = CFRetain(CFSTR("arrowCursor")); |
| } |
| |
| done: |
| TRACE("setting cursor with cursor_name %s cursor_frames %p\n", debugstr_cf(cursor_name), cursor_frames); |
| macdrv_set_cursor(cursor_name, cursor_frames); |
| if (cursor_name) CFRelease(cursor_name); |
| if (cursor_frames) CFRelease(cursor_frames); |
| } |
| |
| |
| /*********************************************************************** |
| * SetCursorPos (MACDRV.@) |
| */ |
| BOOL CDECL macdrv_SetCursorPos(INT x, INT y) |
| { |
| BOOL ret = macdrv_set_cursor_position(CGPointMake(x, y)); |
| if (ret) |
| TRACE("warped to %d,%d\n", x, y); |
| else |
| ERR("failed to warp to %d,%d\n", x, y); |
| return ret; |
| } |
| |
| |
| /*********************************************************************** |
| * macdrv_mouse_button |
| * |
| * Handler for MOUSE_BUTTON events. |
| */ |
| void macdrv_mouse_button(HWND hwnd, const macdrv_event *event) |
| { |
| UINT flags = 0; |
| WORD data = 0; |
| |
| TRACE("win %p button %d %s at (%d,%d) time %lu (%lu ticks ago)\n", hwnd, event->mouse_button.button, |
| (event->mouse_button.pressed ? "pressed" : "released"), |
| event->mouse_button.x, event->mouse_button.y, |
| event->mouse_button.time_ms, (GetTickCount() - event->mouse_button.time_ms)); |
| |
| if (event->mouse_button.pressed) |
| { |
| switch (event->mouse_button.button) |
| { |
| case 0: flags |= MOUSEEVENTF_LEFTDOWN; break; |
| case 1: flags |= MOUSEEVENTF_RIGHTDOWN; break; |
| case 2: flags |= MOUSEEVENTF_MIDDLEDOWN; break; |
| default: |
| flags |= MOUSEEVENTF_XDOWN; |
| data = 1 << (event->mouse_button.button - 3); |
| break; |
| } |
| } |
| else |
| { |
| switch (event->mouse_button.button) |
| { |
| case 0: flags |= MOUSEEVENTF_LEFTUP; break; |
| case 1: flags |= MOUSEEVENTF_RIGHTUP; break; |
| case 2: flags |= MOUSEEVENTF_MIDDLEUP; break; |
| default: |
| flags |= MOUSEEVENTF_XUP; |
| data = 1 << (event->mouse_button.button - 3); |
| break; |
| } |
| } |
| |
| send_mouse_input(hwnd, event->window, flags | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, |
| event->mouse_button.x, event->mouse_button.y, |
| data, FALSE, event->mouse_button.time_ms); |
| } |
| |
| |
| /*********************************************************************** |
| * macdrv_mouse_moved |
| * |
| * Handler for MOUSE_MOVED and MOUSE_MOVED_ABSOLUTE events. |
| */ |
| void macdrv_mouse_moved(HWND hwnd, const macdrv_event *event) |
| { |
| UINT flags = MOUSEEVENTF_MOVE; |
| |
| TRACE("win %p/%p %s (%d,%d) drag %d time %lu (%lu ticks ago)\n", hwnd, event->window, |
| (event->type == MOUSE_MOVED) ? "relative" : "absolute", |
| event->mouse_moved.x, event->mouse_moved.y, event->mouse_moved.drag, |
| event->mouse_moved.time_ms, (GetTickCount() - event->mouse_moved.time_ms)); |
| |
| if (event->type == MOUSE_MOVED_ABSOLUTE) |
| flags |= MOUSEEVENTF_ABSOLUTE; |
| |
| send_mouse_input(hwnd, event->window, flags, event->mouse_moved.x, event->mouse_moved.y, |
| 0, event->mouse_moved.drag, event->mouse_moved.time_ms); |
| } |
| |
| |
| /*********************************************************************** |
| * macdrv_mouse_scroll |
| * |
| * Handler for MOUSE_SCROLL events. |
| */ |
| void macdrv_mouse_scroll(HWND hwnd, const macdrv_event *event) |
| { |
| TRACE("win %p/%p scroll (%d,%d) at (%d,%d) time %lu (%lu ticks ago)\n", hwnd, |
| event->window, event->mouse_scroll.x_scroll, event->mouse_scroll.y_scroll, |
| event->mouse_scroll.x, event->mouse_scroll.y, |
| event->mouse_scroll.time_ms, (GetTickCount() - event->mouse_scroll.time_ms)); |
| |
| if (event->mouse_scroll.y_scroll) |
| send_mouse_input(hwnd, event->window, MOUSEEVENTF_WHEEL | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, |
| event->mouse_scroll.x, event->mouse_scroll.y, |
| event->mouse_scroll.y_scroll, FALSE, event->mouse_scroll.time_ms); |
| if (event->mouse_scroll.x_scroll) |
| send_mouse_input(hwnd, event->window, MOUSEEVENTF_HWHEEL | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, |
| event->mouse_scroll.x, event->mouse_scroll.y, |
| event->mouse_scroll.x_scroll, FALSE, event->mouse_scroll.time_ms); |
| } |
| |
| |
| /*********************************************************************** |
| * macdrv_release_capture |
| * |
| * Handler for RELEASE_CAPTURE events. |
| */ |
| void macdrv_release_capture(HWND hwnd, const macdrv_event *event) |
| { |
| struct macdrv_thread_data *thread_data = macdrv_thread_data(); |
| HWND capture = GetCapture(); |
| HWND capture_top = GetAncestor(capture, GA_ROOT); |
| |
| TRACE("win %p/%p thread_data->capture_window %p GetCapture() %p in %p\n", hwnd, |
| event->window, thread_data->capture_window, capture, capture_top); |
| |
| if (event->window == thread_data->capture_window && hwnd == capture_top) |
| { |
| ReleaseCapture(); |
| if (!PostMessageW(capture, WM_CANCELMODE, 0, 0)) |
| WARN("failed to post WM_CANCELMODE; error 0x%08x\n", GetLastError()); |
| } |
| } |