diff --git a/dlls/wineandroid.drv/WineActivity.java b/dlls/wineandroid.drv/WineActivity.java index e9ab1f2b2e2..4cfe8e0a866 100644 --- a/dlls/wineandroid.drv/WineActivity.java +++ b/dlls/wineandroid.drv/WineActivity.java @@ -25,6 +25,7 @@ import android.app.ProgressDialog; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.Build; @@ -34,6 +35,7 @@ import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.PointerIcon; import android.view.Surface; import android.view.TextureView; import android.view.View; @@ -64,6 +66,7 @@ public class WineActivity extends Activity protected WineWindow desktop_window; protected WineWindow message_window; + private PointerIcon current_cursor; @Override public void onCreate(Bundle savedInstanceState) @@ -684,6 +687,12 @@ public void onSurfaceTextureUpdated(SurfaceTexture surftex) { } + @TargetApi(24) + public PointerIcon onResolvePointerIcon( MotionEvent event, int index ) + { + return current_cursor; + } + public boolean onGenericMotionEvent( MotionEvent event ) { if (is_client) return false; // let the whole window handle it @@ -799,6 +808,18 @@ public void set_window_parent( int hwnd, int parent, float scale, int pid ) if (win.parent == desktop_window) win.create_whole_view(); } + @TargetApi(24) + public void set_cursor( int id, int width, int height, int hotspotx, int hotspoty, int bits[] ) + { + Log.i( LOGTAG, String.format( "set_cursor id %d size %dx%d hotspot %dx%d", id, width, height, hotspotx, hotspoty )); + if (bits != null) + { + Bitmap bitmap = Bitmap.createBitmap( bits, width, height, Bitmap.Config.ARGB_8888 ); + current_cursor = PointerIcon.create( bitmap, hotspotx, hotspoty ); + } + else current_cursor = PointerIcon.getSystemIcon( this, id ); + } + public void window_pos_changed( int hwnd, int flags, int insert_after, int owner, int style, Rect window_rect, Rect client_rect, Rect visible_rect ) { @@ -827,6 +848,13 @@ public void setParent( final int hwnd, final int parent, final float scale, fina runOnUiThread( new Runnable() { public void run() { set_window_parent( hwnd, parent, scale, pid ); }} ); } + public void setCursor( final int id, final int width, final int height, + final int hotspotx, final int hotspoty, final int bits[] ) + { + if (Build.VERSION.SDK_INT < 24) return; + runOnUiThread( new Runnable() { public void run() { set_cursor( id, width, height, hotspotx, hotspoty, bits ); }} ); + } + public void windowPosChanged( final int hwnd, final int flags, final int insert_after, final int owner, final int style, final int window_left, final int window_top, diff --git a/dlls/wineandroid.drv/android.h b/dlls/wineandroid.drv/android.h index b218a9b4aa8..befaa3c27df 100644 --- a/dlls/wineandroid.drv/android.h +++ b/dlls/wineandroid.drv/android.h @@ -72,6 +72,8 @@ extern int ioctl_window_pos_changed( HWND hwnd, const RECT *window_rect, const R HWND after, HWND owner ) DECLSPEC_HIDDEN; extern int ioctl_set_window_parent( HWND hwnd, HWND parent, float scale ) DECLSPEC_HIDDEN; extern int ioctl_set_capture( HWND hwnd ) DECLSPEC_HIDDEN; +extern int ioctl_set_cursor( int id, int width, int height, + int hotspotx, int hotspoty, const unsigned int *bits ) DECLSPEC_HIDDEN; /************************************************************************** @@ -151,7 +153,7 @@ union event_data } kbd; }; -int send_event( const union event_data *data ); +int send_event( const union event_data *data ) DECLSPEC_HIDDEN; extern JavaVM *wine_get_java_vm(void); extern jobject wine_get_java_object(void); diff --git a/dlls/wineandroid.drv/build.gradle.in b/dlls/wineandroid.drv/build.gradle.in index 4bd98b907f4..7271c49e573 100644 --- a/dlls/wineandroid.drv/build.gradle.in +++ b/dlls/wineandroid.drv/build.gradle.in @@ -58,7 +58,7 @@ tasks.whenTaskAdded { t -> android { - compileSdkVersion 21 + compileSdkVersion 25 buildToolsVersion "25.0.3" defaultConfig diff --git a/dlls/wineandroid.drv/device.c b/dlls/wineandroid.drv/device.c index c9a9a95f884..0800ad79475 100644 --- a/dlls/wineandroid.drv/device.c +++ b/dlls/wineandroid.drv/device.c @@ -70,6 +70,7 @@ enum android_ioctl IOCTL_PERFORM, IOCTL_SET_SWAP_INT, IOCTL_SET_CAPTURE, + IOCTL_SET_CURSOR, NB_IOCTLS }; @@ -212,6 +213,17 @@ struct ioctl_android_set_capture struct ioctl_header hdr; }; +struct ioctl_android_set_cursor +{ + struct ioctl_header hdr; + int id; + int width; + int height; + int hotspotx; + int hotspoty; + int bits[1]; +}; + static struct gralloc_module_t *gralloc_module; static struct gralloc1_device *gralloc1_device; static BOOL gralloc1_caps[GRALLOC1_LAST_CAPABILITY + 1]; @@ -1041,6 +1053,44 @@ static NTSTATUS setCapture_ioctl( void *data, DWORD in_size, DWORD out_size, ULO return STATUS_SUCCESS; } +static NTSTATUS setCursor_ioctl( void *data, DWORD in_size, DWORD out_size, ULONG_PTR *ret_size ) +{ + static jmethodID method; + jobject object; + int size; + struct ioctl_android_set_cursor *res = data; + + if (in_size < offsetof( struct ioctl_android_set_cursor, bits )) return STATUS_INVALID_PARAMETER; + + if (res->width < 0 || res->height < 0 || res->width > 256 || res->height > 256) + return STATUS_INVALID_PARAMETER; + + size = res->width * res->height; + if (in_size != offsetof( struct ioctl_android_set_cursor, bits[size] )) + return STATUS_INVALID_PARAMETER; + + TRACE( "hwnd %08x size %d\n", res->hdr.hwnd, size ); + + if (!(object = load_java_method( &method, "setCursor", "(IIIII[I)V" ))) + return STATUS_NOT_SUPPORTED; + + wrap_java_call(); + + if (size) + { + jintArray array = (*jni_env)->NewIntArray( jni_env, size ); + (*jni_env)->SetIntArrayRegion( jni_env, array, 0, size, (jint *)res->bits ); + (*jni_env)->CallVoidMethod( jni_env, object, method, 0, res->width, res->height, + res->hotspotx, res->hotspoty, array ); + (*jni_env)->DeleteLocalRef( jni_env, array ); + } + else (*jni_env)->CallVoidMethod( jni_env, object, method, res->id, 0, 0, 0, 0, 0 ); + + unwrap_java_call(); + + return STATUS_SUCCESS; +} + typedef NTSTATUS (*ioctl_func)( void *in, DWORD in_size, DWORD out_size, ULONG_PTR *ret_size ); static const ioctl_func ioctl_funcs[] = { @@ -1055,6 +1105,7 @@ static const ioctl_func ioctl_funcs[] = perform_ioctl, /* IOCTL_PERFORM */ setSwapInterval_ioctl, /* IOCTL_SET_SWAP_INT */ setCapture_ioctl, /* IOCTL_SET_CAPTURE */ + setCursor_ioctl, /* IOCTL_SET_CURSOR */ }; static NTSTATUS WINAPI ioctl_callback( DEVICE_OBJECT *device, IRP *irp ) @@ -1584,3 +1635,24 @@ int ioctl_set_capture( HWND hwnd ) req.hdr.opengl = FALSE; return android_ioctl( IOCTL_SET_CAPTURE, &req, sizeof(req), NULL, NULL ); } + +int ioctl_set_cursor( int id, int width, int height, + int hotspotx, int hotspoty, const unsigned int *bits ) +{ + struct ioctl_android_set_cursor *req; + unsigned int size = offsetof( struct ioctl_android_set_cursor, bits[width * height] ); + int ret; + + if (!(req = HeapAlloc( GetProcessHeap(), 0, size ))) return -ENOMEM; + req->hdr.hwnd = 0; /* unused */ + req->hdr.opengl = FALSE; + req->id = id; + req->width = width; + req->height = height; + req->hotspotx = hotspotx; + req->hotspoty = hotspoty; + memcpy( req->bits, bits, width * height * sizeof(req->bits[0]) ); + ret = android_ioctl( IOCTL_SET_CURSOR, req, size, NULL, NULL ); + HeapFree( GetProcessHeap(), 0, req ); + return ret; +} diff --git a/dlls/wineandroid.drv/window.c b/dlls/wineandroid.drv/window.c index aebe4c57b35..861b2168527 100644 --- a/dlls/wineandroid.drv/window.c +++ b/dlls/wineandroid.drv/window.c @@ -37,6 +37,7 @@ # include #endif +#define OEMRESOURCE #include "windef.h" #include "winbase.h" #include "wingdi.h" @@ -966,6 +967,226 @@ static void set_surface_layered( struct window_surface *window_surface, BYTE alp window_surface->funcs->unlock( window_surface ); } +/*********************************************************************** + * get_mono_icon_argb + * + * Return a monochrome icon/cursor bitmap bits in ARGB format. + */ +static unsigned int *get_mono_icon_argb( HDC hdc, HBITMAP bmp, unsigned int *width, unsigned int *height ) +{ + BITMAP bm; + char *mask; + unsigned int i, j, stride, mask_size, bits_size, *bits = NULL, *ptr; + + if (!GetObjectW( bmp, sizeof(bm), &bm )) return NULL; + stride = ((bm.bmWidth + 15) >> 3) & ~1; + mask_size = stride * bm.bmHeight; + if (!(mask = HeapAlloc( GetProcessHeap(), 0, mask_size ))) return NULL; + if (!GetBitmapBits( bmp, mask_size, mask )) goto done; + + bm.bmHeight /= 2; + bits_size = bm.bmWidth * bm.bmHeight * sizeof(*bits); + if (!(bits = HeapAlloc( GetProcessHeap(), 0, bits_size ))) goto done; + + ptr = bits; + for (i = 0; i < bm.bmHeight; i++) + for (j = 0; j < bm.bmWidth; j++, ptr++) + { + int and = ((mask[i * stride + j / 8] << (j % 8)) & 0x80); + int xor = ((mask[(i + bm.bmHeight) * stride + j / 8] << (j % 8)) & 0x80); + if (!xor && and) + *ptr = 0; + else if (xor && !and) + *ptr = 0xffffffff; + else + /* we can't draw "invert" pixels, so render them as black instead */ + *ptr = 0xff000000; + } + + *width = bm.bmWidth; + *height = bm.bmHeight; + +done: + HeapFree( GetProcessHeap(), 0, mask ); + return bits; +} + +/*********************************************************************** + * get_bitmap_argb + * + * Return the bitmap bits in ARGB format. Helper for setting icons and cursors. + */ +static unsigned int *get_bitmap_argb( HDC hdc, HBITMAP color, HBITMAP mask, unsigned int *width, + unsigned int *height ) +{ + char buffer[FIELD_OFFSET( BITMAPINFO, bmiColors[256] )]; + BITMAPINFO *info = (BITMAPINFO *)buffer; + BITMAP bm; + unsigned int *ptr, *bits = NULL; + unsigned char *mask_bits = NULL; + int i, j; + BOOL has_alpha = FALSE; + + if (!color) return get_mono_icon_argb( hdc, mask, width, height ); + + if (!GetObjectW( color, sizeof(bm), &bm )) return NULL; + info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info->bmiHeader.biWidth = bm.bmWidth; + info->bmiHeader.biHeight = -bm.bmHeight; + info->bmiHeader.biPlanes = 1; + info->bmiHeader.biBitCount = 32; + info->bmiHeader.biCompression = BI_RGB; + info->bmiHeader.biSizeImage = bm.bmWidth * bm.bmHeight * 4; + info->bmiHeader.biXPelsPerMeter = 0; + info->bmiHeader.biYPelsPerMeter = 0; + info->bmiHeader.biClrUsed = 0; + info->bmiHeader.biClrImportant = 0; + if (!(bits = HeapAlloc( GetProcessHeap(), 0, bm.bmWidth * bm.bmHeight * sizeof(unsigned int) ))) + goto failed; + if (!GetDIBits( hdc, color, 0, bm.bmHeight, bits, info, DIB_RGB_COLORS )) goto failed; + + *width = bm.bmWidth; + *height = bm.bmHeight; + + for (i = 0; i < bm.bmWidth * bm.bmHeight; i++) + if ((has_alpha = (bits[i] & 0xff000000) != 0)) break; + + if (!has_alpha) + { + unsigned int width_bytes = (bm.bmWidth + 31) / 32 * 4; + /* generate alpha channel from the mask */ + info->bmiHeader.biBitCount = 1; + info->bmiHeader.biSizeImage = width_bytes * bm.bmHeight; + if (!(mask_bits = HeapAlloc( GetProcessHeap(), 0, info->bmiHeader.biSizeImage ))) goto failed; + if (!GetDIBits( hdc, mask, 0, bm.bmHeight, mask_bits, info, DIB_RGB_COLORS )) goto failed; + ptr = bits; + for (i = 0; i < bm.bmHeight; i++) + for (j = 0; j < bm.bmWidth; j++, ptr++) + if (!((mask_bits[i * width_bytes + j / 8] << (j % 8)) & 0x80)) *ptr |= 0xff000000; + HeapFree( GetProcessHeap(), 0, mask_bits ); + } + + return bits; + +failed: + HeapFree( GetProcessHeap(), 0, bits ); + HeapFree( GetProcessHeap(), 0, mask_bits ); + *width = *height = 0; + return NULL; +} + + +enum android_system_cursors +{ + TYPE_ARROW = 1000, + TYPE_CONTEXT_MENU = 1001, + TYPE_HAND = 1002, + TYPE_HELP = 1003, + TYPE_WAIT = 1004, + TYPE_CELL = 1006, + TYPE_CROSSHAIR = 1007, + TYPE_TEXT = 1008, + TYPE_VERTICAL_TEXT = 1009, + TYPE_ALIAS = 1010, + TYPE_COPY = 1011, + TYPE_NO_DROP = 1012, + TYPE_ALL_SCROLL = 1013, + TYPE_HORIZONTAL_DOUBLE_ARROW = 1014, + TYPE_VERTICAL_DOUBLE_ARROW = 1015, + TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016, + TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017, + TYPE_ZOOM_IN = 1018, + TYPE_ZOOM_OUT = 1019, + TYPE_GRAB = 1020, + TYPE_GRABBING = 1021, +}; + +struct system_cursors +{ + WORD id; + enum android_system_cursors android_id; +}; + +static const struct system_cursors user32_cursors[] = +{ + { OCR_NORMAL, TYPE_ARROW }, + { OCR_IBEAM, TYPE_TEXT }, + { OCR_WAIT, TYPE_WAIT }, + { OCR_CROSS, TYPE_CROSSHAIR }, + { OCR_SIZE, TYPE_ALL_SCROLL }, + { OCR_SIZEALL, TYPE_ALL_SCROLL }, + { OCR_SIZENWSE, TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW }, + { OCR_SIZENESW, TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW }, + { OCR_SIZEWE, TYPE_HORIZONTAL_DOUBLE_ARROW }, + { OCR_SIZENS, TYPE_VERTICAL_DOUBLE_ARROW }, + { OCR_NO, TYPE_NO_DROP }, + { OCR_HAND, TYPE_HAND }, + { OCR_HELP, TYPE_HELP }, + { 0 } +}; + +static const struct system_cursors comctl32_cursors[] = +{ + /* 102 TYPE_MOVE doesn't exist */ + { 104, TYPE_COPY }, + { 105, TYPE_ARROW }, + { 106, TYPE_HORIZONTAL_DOUBLE_ARROW }, + { 107, TYPE_HORIZONTAL_DOUBLE_ARROW }, + { 108, TYPE_GRABBING }, + { 135, TYPE_VERTICAL_DOUBLE_ARROW }, + { 0 } +}; + +static const struct system_cursors ole32_cursors[] = +{ + { 1, TYPE_NO_DROP }, + /* 2 TYPE_MOVE doesn't exist */ + { 3, TYPE_COPY }, + { 4, TYPE_ALIAS }, + { 0 } +}; + +static const struct system_cursors riched20_cursors[] = +{ + { 105, TYPE_GRABBING }, + { 109, TYPE_COPY }, + /* 110 TYPE_MOVE doesn't exist */ + { 111, TYPE_NO_DROP }, + { 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} } +}; + +static int get_cursor_system_id( const ICONINFOEXW *info ) +{ + const struct system_cursors *cursors; + unsigned int i; + HMODULE module; + + if (info->szResName[0]) return 0; /* only integer resources are supported here */ + if (!(module = GetModuleHandleW( info->szModName ))) return 0; + + 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])) return 0; + + cursors = module_cursors[i].cursors; + for (i = 0; cursors[i].id; i++) + if (cursors[i].id == info->wResID) return cursors[i].android_id; + + return 0; +} + static WNDPROC desktop_orig_wndproc; @@ -1204,6 +1425,51 @@ void CDECL ANDROID_SetCapture( HWND hwnd, UINT flags ) } +/*********************************************************************** + * ANDROID_SetCursor + */ +void CDECL ANDROID_SetCursor( HCURSOR handle ) +{ + static HCURSOR last_cursor; + static DWORD last_cursor_change; + + if (InterlockedExchangePointer( (void **)&last_cursor, handle ) != handle || + GetTickCount() - last_cursor_change > 100) + { + last_cursor_change = GetTickCount(); + + if (handle) + { + unsigned int width = 0, height = 0, *bits = NULL; + ICONINFOEXW info; + int id; + + info.cbSize = sizeof(info); + if (!GetIconInfoExW( handle, &info )) return; + + if (!(id = get_cursor_system_id( &info ))) + { + HDC hdc = CreateCompatibleDC( 0 ); + bits = get_bitmap_argb( hdc, info.hbmColor, info.hbmMask, &width, &height ); + DeleteDC( hdc ); + + /* make sure hotspot is valid */ + if (info.xHotspot >= width || info.yHotspot >= height) + { + info.xHotspot = width / 2; + info.yHotspot = height / 2; + } + } + ioctl_set_cursor( id, width, height, info.xHotspot, info.yHotspot, bits ); + HeapFree( GetProcessHeap(), 0, bits ); + DeleteObject( info.hbmColor ); + DeleteObject( info.hbmMask ); + } + else ioctl_set_cursor( 0, 0, 0, 0, 0, NULL ); + } +} + + /*********************************************************************** * ANDROID_SetWindowStyle */ diff --git a/dlls/wineandroid.drv/wineandroid.drv.spec b/dlls/wineandroid.drv/wineandroid.drv.spec index 99a3af88c09..00de23de27e 100644 --- a/dlls/wineandroid.drv/wineandroid.drv.spec +++ b/dlls/wineandroid.drv/wineandroid.drv.spec @@ -9,6 +9,7 @@ @ cdecl MapVirtualKeyEx(long long long) ANDROID_MapVirtualKeyEx @ cdecl ToUnicodeEx(long long ptr ptr long long long) ANDROID_ToUnicodeEx @ cdecl VkKeyScanEx(long long) ANDROID_VkKeyScanEx +@ cdecl SetCursor(long) ANDROID_SetCursor @ cdecl ChangeDisplaySettingsEx(ptr ptr long long long) ANDROID_ChangeDisplaySettingsEx @ cdecl EnumDisplayMonitors(long ptr ptr long) ANDROID_EnumDisplayMonitors @ cdecl EnumDisplaySettingsEx(ptr long ptr long) ANDROID_EnumDisplaySettingsEx