/* * Cursor and icon support * * Copyright 1995 Alexandre Julliard * Copyright 1996 Martin Von Loewis * Copyright 1997 Alex Korobka * Copyright 1998 Turchanov Sergey * Copyright 2007 Henri Verbeet * Copyright 2009 Vincent Povirk for CodeWeavers * Copyright 2016 Dmitry Timoshkov * * 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 */ #if 0 #pragma makedep unix #endif #include #include "ntgdi_private.h" #include "ntuser_private.h" #include "wine/server.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(cursor); WINE_DECLARE_DEBUG_CHANNEL(icon); struct cursoricon_object { struct user_object obj; /* object header */ struct list entry; /* entry in shared icons list */ ULONG_PTR param; /* opaque param used by 16-bit code */ UNICODE_STRING module; /* module for icons loaded from resources */ WCHAR *resname; /* resource name for icons loaded from resources */ HRSRC rsrc; /* resource for shared icons */ BOOL is_shared; /* whether this object is shared */ BOOL is_icon; /* whether icon or cursor */ BOOL is_ani; /* whether this object is a static cursor or an animated cursor */ UINT delay; /* delay between this frame and the next (in jiffies) */ union { struct cursoricon_frame frame; /* frame-specific icon data */ struct { UINT num_frames; /* number of frames in the icon/cursor */ UINT num_steps; /* number of sequence steps in the icon/cursor */ HICON *frames; /* list of animated cursor frames */ } ani; }; }; static struct list icon_cache = LIST_INIT( icon_cache ); static struct cursoricon_object *get_icon_ptr( HICON handle ) { struct cursoricon_object *obj = get_user_handle_ptr( handle, NTUSER_OBJ_ICON ); if (obj == OBJ_OTHER_PROCESS) { WARN( "icon handle %p from other process\n", handle ); obj = NULL; } return obj; } /*********************************************************************** * NtUserShowCursor (win32u.@) */ INT WINAPI NtUserShowCursor( BOOL show ) { HCURSOR cursor; int increment = show ? 1 : -1; int count; SERVER_START_REQ( set_cursor ) { req->flags = SET_CURSOR_COUNT; req->show_count = increment; wine_server_call( req ); cursor = wine_server_ptr_handle( reply->prev_handle ); count = reply->prev_count + increment; } SERVER_END_REQ; TRACE("%d, count=%d\n", show, count ); if (show && !count) user_driver->pSetCursor( cursor ); else if (!show && count == -1) user_driver->pSetCursor( 0 ); return count; } /*********************************************************************** * NtUserSetCursor (win32u.@) */ HCURSOR WINAPI NtUserSetCursor( HCURSOR cursor ) { struct cursoricon_object *obj; HCURSOR old_cursor; int show_count; BOOL ret; TRACE( "%p\n", cursor ); SERVER_START_REQ( set_cursor ) { req->flags = SET_CURSOR_HANDLE; req->handle = wine_server_user_handle( cursor ); if ((ret = !wine_server_call_err( req ))) { old_cursor = wine_server_ptr_handle( reply->prev_handle ); show_count = reply->prev_count; } } SERVER_END_REQ; if (!ret) return 0; user_driver->pSetCursor( show_count >= 0 ? cursor : 0 ); if (!(obj = get_icon_ptr( old_cursor ))) return 0; release_user_handle_ptr( obj ); return old_cursor; } /*********************************************************************** * NtUserGetCursor (win32u.@) */ HCURSOR WINAPI NtUserGetCursor(void) { HCURSOR ret; SERVER_START_REQ( set_cursor ) { req->flags = 0; wine_server_call( req ); ret = wine_server_ptr_handle( reply->prev_handle ); } SERVER_END_REQ; return ret; } /*********************************************************************** * NtUserClipCursor (win32u.@) */ BOOL WINAPI NtUserClipCursor( const RECT *rect ) { UINT dpi; BOOL ret; RECT new_rect; TRACE( "Clipping to %s\n", wine_dbgstr_rect(rect) ); if (rect) { if (rect->left > rect->right || rect->top > rect->bottom) return FALSE; if ((dpi = get_thread_dpi())) { HMONITOR monitor = monitor_from_rect( rect, MONITOR_DEFAULTTOPRIMARY, dpi ); new_rect = map_dpi_rect( *rect, dpi, get_monitor_dpi( monitor )); rect = &new_rect; } } SERVER_START_REQ( set_cursor ) { req->clip_msg = WM_WINE_CLIPCURSOR; if (rect) { req->flags = SET_CURSOR_CLIP; req->clip.left = rect->left; req->clip.top = rect->top; req->clip.right = rect->right; req->clip.bottom = rect->bottom; } else req->flags = SET_CURSOR_NOCLIP; if ((ret = !wine_server_call( req ))) { new_rect.left = reply->new_clip.left; new_rect.top = reply->new_clip.top; new_rect.right = reply->new_clip.right; new_rect.bottom = reply->new_clip.bottom; } } SERVER_END_REQ; if (ret) user_driver->pClipCursor( &new_rect ); return ret; } BOOL get_clip_cursor( RECT *rect ) { UINT dpi; BOOL ret; if (!rect) return FALSE; SERVER_START_REQ( set_cursor ) { req->flags = 0; if ((ret = !wine_server_call( req ))) { rect->left = reply->new_clip.left; rect->top = reply->new_clip.top; rect->right = reply->new_clip.right; rect->bottom = reply->new_clip.bottom; } } SERVER_END_REQ; if (ret && (dpi = get_thread_dpi())) { HMONITOR monitor = monitor_from_rect( rect, MONITOR_DEFAULTTOPRIMARY, 0 ); *rect = map_dpi_rect( *rect, get_monitor_dpi( monitor ), dpi ); } return ret; } HICON alloc_cursoricon_handle( BOOL is_icon ) { struct cursoricon_object *obj; HICON handle; if (!(obj = calloc( 1, sizeof(*obj) ))) return NULL; obj->is_icon = is_icon; if (!(handle = alloc_user_handle( &obj->obj, NTUSER_OBJ_ICON ))) free( obj ); return handle; } static struct cursoricon_object *get_icon_frame_ptr( HICON handle, UINT step ) { struct cursoricon_object *obj, *ret; if (!(obj = get_icon_ptr( handle ))) return NULL; if (!obj->is_ani) return obj; if (step >= obj->ani.num_steps) { release_user_handle_ptr( obj ); return NULL; } ret = get_icon_ptr( obj->ani.frames[step] ); release_user_handle_ptr( obj ); return ret; } static BOOL free_icon_handle( HICON handle ) { struct cursoricon_object *obj = free_user_handle( handle, NTUSER_OBJ_ICON ); if (obj == OBJ_OTHER_PROCESS) WARN( "icon handle %p from other process\n", handle ); else if (obj) { ULONG param = obj->param; void *ret_ptr; ULONG ret_len; UINT i; assert( !obj->rsrc ); /* shared icons can't be freed */ if (!obj->is_ani) { if (obj->frame.alpha) NtGdiDeleteObjectApp( obj->frame.alpha ); if (obj->frame.color) NtGdiDeleteObjectApp( obj->frame.color ); if (obj->frame.mask) NtGdiDeleteObjectApp( obj->frame.mask ); } else { for (i = 0; i < obj->ani.num_steps; i++) { HICON hFrame = obj->ani.frames[i]; if (hFrame) { UINT j; free_icon_handle( obj->ani.frames[i] ); for (j = 0; j < obj->ani.num_steps; j++) { if (obj->ani.frames[j] == hFrame) obj->ani.frames[j] = 0; } } } free( obj->ani.frames ); } if (!IS_INTRESOURCE( obj->resname )) free( obj->resname ); free( obj ); if (param) KeUserModeCallback( NtUserCallFreeIcon, ¶m, sizeof(param), &ret_ptr, &ret_len ); user_driver->pDestroyCursorIcon( handle ); return TRUE; } return FALSE; } /*********************************************************************** * NtUserDestroyCursor (win32u.@) */ BOOL WINAPI NtUserDestroyCursor( HCURSOR cursor, ULONG arg ) { struct cursoricon_object *obj; BOOL shared, ret; TRACE( "%p\n", cursor ); if (!(obj = get_icon_ptr( cursor ))) return FALSE; shared = obj->is_shared; release_user_handle_ptr( obj ); ret = NtUserGetCursor() != cursor; if (!shared) free_icon_handle( cursor ); return ret; } /*********************************************************************** * NtUserSetCursorIconData (win32u.@) */ BOOL WINAPI NtUserSetCursorIconData( HCURSOR cursor, UNICODE_STRING *module, UNICODE_STRING *res_name, struct cursoricon_desc *desc ) { struct cursoricon_object *obj; UINT i, j; if (!(obj = get_icon_ptr( cursor ))) return FALSE; if (obj->is_ani || obj->frame.width) { /* already initialized */ release_user_handle_ptr( obj ); SetLastError( ERROR_INVALID_CURSOR_HANDLE ); return FALSE; } obj->delay = desc->delay; if (desc->num_steps) { if (!(obj->ani.frames = calloc( desc->num_steps, sizeof(*obj->ani.frames) ))) { release_user_handle_ptr( obj ); return FALSE; } obj->is_ani = TRUE; obj->ani.num_steps = desc->num_steps; obj->ani.num_frames = desc->num_frames; } else obj->frame = desc->frames[0]; if (!res_name) obj->resname = NULL; else if (res_name->Length) { obj->resname = malloc( res_name->Length + sizeof(WCHAR) ); if (obj->resname) { memcpy( obj->resname, res_name->Buffer, res_name->Length ); obj->resname[res_name->Length / sizeof(WCHAR)] = 0; } } else obj->resname = MAKEINTRESOURCEW( LOWORD(res_name->Buffer) ); if (module && module->Length && (obj->module.Buffer = malloc( module->Length ))) { memcpy( obj->module.Buffer, module->Buffer, module->Length ); obj->module.Length = module->Length; } if (obj->is_ani) { /* Setup the animated frames in the correct sequence */ for (i = 0; i < desc->num_steps; i++) { struct cursoricon_desc frame_desc; DWORD frame_id; if (obj->ani.frames[i]) continue; /* already set */ frame_id = desc->frame_seq ? desc->frame_seq[i] : i; if (frame_id >= obj->ani.num_frames) { frame_id = obj->ani.num_frames - 1; ERR_(cursor)( "Sequence indicates frame past end of list, corrupt?\n" ); } memset( &frame_desc, 0, sizeof(frame_desc) ); frame_desc.delay = desc->frame_rates ? desc->frame_rates[i] : desc->delay; frame_desc.frames = &desc->frames[frame_id]; if (!(obj->ani.frames[i] = alloc_cursoricon_handle( obj->is_icon )) || !NtUserSetCursorIconData( obj->ani.frames[i], NULL, NULL, &frame_desc )) { release_user_handle_ptr( obj ); return 0; } if (desc->frame_seq) { for (j = i + 1; j < obj->ani.num_steps; j++) { if (desc->frame_seq[j] == frame_id) obj->ani.frames[j] = obj->ani.frames[i]; } } } } if (desc->flags & LR_SHARED) { obj->is_shared = TRUE; if (obj->module.Length) { obj->rsrc = desc->rsrc; list_add_head( &icon_cache, &obj->entry ); } } release_user_handle_ptr( obj ); return TRUE; } /*********************************************************************** * NtUserFindExistingCursorIcon (win32u.@) */ HICON WINAPI NtUserFindExistingCursorIcon( UNICODE_STRING *module, UNICODE_STRING *res_name, void *desc ) { struct cursoricon_object *ptr; HICON ret = 0; user_lock(); LIST_FOR_EACH_ENTRY( ptr, &icon_cache, struct cursoricon_object, entry ) { if (ptr->module.Length != module->Length) continue; if (memcmp( ptr->module.Buffer, module->Buffer, module->Length )) continue; /* We pass rsrc as desc argument, this is not compatible with Windows */ if (ptr->rsrc != desc) continue; ret = ptr->obj.handle; break; } user_unlock(); return ret; } /*********************************************************************** * NtUserGetIconSize (win32u.@) */ BOOL WINAPI NtUserGetIconSize( HICON handle, UINT step, LONG *width, LONG *height ) { struct cursoricon_object *obj; if (!(obj = get_icon_frame_ptr( handle, step ))) { SetLastError( ERROR_INVALID_CURSOR_HANDLE ); return FALSE; } *width = obj->frame.width; *height = obj->frame.height * 2; release_user_handle_ptr( obj ); return TRUE; } /********************************************************************** * NtUserGetCursorFrameInfo (win32u.@) */ HCURSOR WINAPI NtUserGetCursorFrameInfo( HCURSOR cursor, DWORD istep, DWORD *rate_jiffies, DWORD *num_steps ) { struct cursoricon_object *obj; HCURSOR ret = 0; UINT icon_steps; if (!rate_jiffies || !num_steps) return 0; if (!(obj = get_icon_ptr( cursor ))) return 0; TRACE( "%p => %d %p %p\n", cursor, istep, rate_jiffies, num_steps ); icon_steps = obj->is_ani ? obj->ani.num_steps : 1; if (istep < icon_steps || !obj->is_ani) { UINT icon_frames = 1; if (obj->is_ani) icon_frames = obj->ani.num_frames; if (obj->is_ani && icon_frames > 1) ret = obj->ani.frames[istep]; else ret = cursor; if (icon_frames == 1) { *rate_jiffies = 0; *num_steps = 1; } else if (icon_steps == 1) { *num_steps = ~0; *rate_jiffies = obj->delay; } else if (istep < icon_steps) { struct cursoricon_object *frame; *num_steps = icon_steps; frame = get_icon_ptr( obj->ani.frames[istep] ); if (obj->ani.num_steps == 1) *num_steps = ~0; else *num_steps = obj->ani.num_steps; *rate_jiffies = frame->delay; release_user_handle_ptr( frame ); } } release_user_handle_ptr( obj ); return ret; } /*********************************************************************** * copy_bitmap * * Helper function to duplicate a bitmap. */ static HBITMAP copy_bitmap( HBITMAP bitmap ) { HDC src, dst = 0; HBITMAP new_bitmap = 0; BITMAP bmp; if (!bitmap) return 0; if (!NtGdiExtGetObjectW( bitmap, sizeof(bmp), &bmp )) return 0; if ((src = NtGdiCreateCompatibleDC( 0 )) && (dst = NtGdiCreateCompatibleDC( 0 ))) { NtGdiSelectBitmap( src, bitmap ); if ((new_bitmap = NtGdiCreateCompatibleBitmap( src, bmp.bmWidth, bmp.bmHeight ))) { NtGdiSelectBitmap( dst, new_bitmap ); NtGdiBitBlt( dst, 0, 0, bmp.bmWidth, bmp.bmHeight, src, 0, 0, SRCCOPY, 0, 0 ); } } NtGdiDeleteObjectApp( dst ); NtGdiDeleteObjectApp( src ); return new_bitmap; } /********************************************************************** * NtUserGetIconInfo (win32u.@) */ BOOL WINAPI NtUserGetIconInfo( HICON icon, ICONINFO *info, UNICODE_STRING *module, UNICODE_STRING *res_name, DWORD *bpp, LONG unk ) { struct cursoricon_object *obj, *frame_obj; BOOL ret = TRUE; if (!(obj = get_icon_ptr( icon ))) { SetLastError( ERROR_INVALID_CURSOR_HANDLE ); return FALSE; } if (!(frame_obj = get_icon_frame_ptr( icon, 0 ))) { release_user_handle_ptr( obj ); return FALSE; } TRACE( "%p => %dx%d\n", icon, frame_obj->frame.width, frame_obj->frame.height ); info->fIcon = obj->is_icon; info->xHotspot = frame_obj->frame.hotspot.x; info->yHotspot = frame_obj->frame.hotspot.y; info->hbmColor = copy_bitmap( frame_obj->frame.color ); info->hbmMask = copy_bitmap( frame_obj->frame.mask ); if (!info->hbmMask || (!info->hbmColor && frame_obj->frame.color)) { NtGdiDeleteObjectApp( info->hbmMask ); NtGdiDeleteObjectApp( info->hbmColor ); ret = FALSE; } else if (obj->module.Length) { if (module) { size_t size = min( module->MaximumLength, obj->module.Length ); if (size) memcpy( module->Buffer, obj->module.Buffer, size ); module->Length = size / sizeof(WCHAR); /* length in chars, not bytes */ } if (res_name) { if (IS_INTRESOURCE( obj->resname )) { res_name->Buffer = obj->resname; res_name->Length = 0; } else { size_t size = min( res_name->MaximumLength, lstrlenW( obj->resname) * sizeof(WCHAR) ); if (size) memcpy( res_name->Buffer, obj->resname, size ); module->Length = size / sizeof(WCHAR); /* length in chars, not bytes */ } } } else { if (module) module->Length = 0; if (res_name) { res_name->Length = 0; res_name->Buffer = NULL; } } release_user_handle_ptr( frame_obj ); release_user_handle_ptr( obj ); return ret; } /****************************************************************************** * NtUserDrawIconEx (win32u.@) */ BOOL WINAPI NtUserDrawIconEx( HDC hdc, INT x0, INT y0, HICON icon, INT width, INT height, UINT step, HBRUSH brush, UINT flags ) { struct cursoricon_object *obj; HBITMAP offscreen_bitmap = 0; HDC hdc_dest, mem_dc; COLORREF old_fg, old_bg; INT x, y, nStretchMode; BOOL result = FALSE; TRACE_(icon)( "(hdc=%p,pos=%d.%d,hicon=%p,extend=%d.%d,step=%d,br=%p,flags=0x%08x)\n", hdc, x0, y0, icon, width, height, step, brush, flags ); if (!(obj = get_icon_frame_ptr( icon, step ))) { FIXME_(icon)("Error retrieving icon frame %d\n", step); return FALSE; } if (!(mem_dc = NtGdiCreateCompatibleDC( hdc ))) { release_user_handle_ptr( obj ); return FALSE; } if (flags & DI_NOMIRROR) FIXME_(icon)("Ignoring flag DI_NOMIRROR\n"); /* Calculate the size of the destination image. */ if (width == 0) { if (flags & DI_DEFAULTSIZE) width = get_system_metrics( SM_CXICON ); else width = obj->frame.width; } if (height == 0) { if (flags & DI_DEFAULTSIZE) height = get_system_metrics( SM_CYICON ); else height = obj->frame.height; } if (get_gdi_object_type( brush ) == NTGDI_OBJ_BRUSH) { HBRUSH prev_brush; RECT r; SetRect(&r, 0, 0, width, width); if (!(hdc_dest = NtGdiCreateCompatibleDC(hdc))) goto failed; if (!(offscreen_bitmap = NtGdiCreateCompatibleBitmap(hdc, width, height))) { NtGdiDeleteObjectApp( hdc_dest ); goto failed; } NtGdiSelectBitmap( hdc_dest, offscreen_bitmap ); prev_brush = NtGdiSelectBrush( hdc_dest, brush ); NtGdiPatBlt( hdc_dest, r.left, r.top, r.right - r.left, r.bottom - r.top, PATCOPY ); if (prev_brush) NtGdiSelectBrush( hdc_dest, prev_brush ); x = y = 0; } else { hdc_dest = hdc; x = x0; y = y0; } nStretchMode = set_stretch_blt_mode( hdc, STRETCH_DELETESCANS ); NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, RGB(0,0,0), &old_fg ); NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, RGB(255,255,255), &old_bg ); if (obj->frame.alpha && (flags & DI_IMAGE)) { BOOL alpha_blend = TRUE; if (get_gdi_object_type( hdc_dest ) == NTGDI_OBJ_MEMDC) { BITMAP bm; HBITMAP bmp = NtGdiGetDCObject( hdc_dest, NTGDI_OBJ_SURF ); alpha_blend = NtGdiExtGetObjectW( bmp, sizeof(bm), &bm ) && bm.bmBitsPixel > 8; } if (alpha_blend) { BLENDFUNCTION pixelblend = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; NtGdiSelectBitmap( mem_dc, obj->frame.alpha ); if (NtGdiAlphaBlend( hdc_dest, x, y, width, height, mem_dc, 0, 0, obj->frame.width, obj->frame.height, pixelblend, 0 )) goto done; } } if (flags & DI_MASK) { DWORD rop = (flags & DI_IMAGE) ? SRCAND : SRCCOPY; NtGdiSelectBitmap( mem_dc, obj->frame.mask ); NtGdiStretchBlt( hdc_dest, x, y, width, height, mem_dc, 0, 0, obj->frame.width, obj->frame.height, rop, 0 ); } if (flags & DI_IMAGE) { if (obj->frame.color) { DWORD rop = (flags & DI_MASK) ? SRCINVERT : SRCCOPY; NtGdiSelectBitmap( mem_dc, obj->frame.color ); NtGdiStretchBlt( hdc_dest, x, y, width, height, mem_dc, 0, 0, obj->frame.width, obj->frame.height, rop, 0 ); } else { DWORD rop = (flags & DI_MASK) ? SRCINVERT : SRCCOPY; NtGdiSelectBitmap( mem_dc, obj->frame.mask ); NtGdiStretchBlt( hdc_dest, x, y, width, height, mem_dc, 0, obj->frame.height, obj->frame.width, obj->frame.height, rop, 0 ); } } done: if (offscreen_bitmap) NtGdiBitBlt( hdc, x0, y0, width, height, hdc_dest, 0, 0, SRCCOPY, 0, 0 ); NtGdiGetAndSetDCDword( hdc, NtGdiSetTextColor, old_fg, NULL ); NtGdiGetAndSetDCDword( hdc, NtGdiSetBkColor, old_bg, NULL ); nStretchMode = set_stretch_blt_mode( hdc, nStretchMode ); result = TRUE; if (hdc_dest != hdc) NtGdiDeleteObjectApp( hdc_dest ); if (offscreen_bitmap) NtGdiDeleteObjectApp( offscreen_bitmap ); failed: NtGdiDeleteObjectApp( mem_dc ); release_user_handle_ptr( obj ); return result; } ULONG_PTR get_icon_param( HICON handle ) { ULONG_PTR ret = 0; struct cursoricon_object *obj = get_user_handle_ptr( handle, NTUSER_OBJ_ICON ); if (obj == OBJ_OTHER_PROCESS) WARN( "icon handle %p from other process\n", handle ); else if (obj) { ret = obj->param; release_user_handle_ptr( obj ); } return ret; } ULONG_PTR set_icon_param( HICON handle, ULONG_PTR param ) { ULONG_PTR ret = 0; struct cursoricon_object *obj = get_user_handle_ptr( handle, NTUSER_OBJ_ICON ); if (obj == OBJ_OTHER_PROCESS) WARN( "icon handle %p from other process\n", handle ); else if (obj) { ret = obj->param; obj->param = param; release_user_handle_ptr( obj ); } return ret; }