mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-11-14 22:38:56 +00:00
974 lines
28 KiB
C
974 lines
28 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
#include "SDL_internal.h"
|
|
|
|
#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
|
|
|
|
#include "SDL_windowsvideo.h"
|
|
#include "SDL_windowsevents.h"
|
|
#include "SDL_windowsrawinput.h"
|
|
|
|
#include "../SDL_video_c.h"
|
|
#include "../../events/SDL_mouse_c.h"
|
|
#include "../../joystick/usb_ids.h"
|
|
#include "../../core/windows/SDL_windows.h" // for checking windows version
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
#define RIFF_FOURCC(c0, c1, c2, c3) \
|
|
((DWORD)(BYTE)(c0) | ((DWORD)(BYTE)(c1) << 8) | \
|
|
((DWORD)(BYTE)(c2) << 16) | ((DWORD)(BYTE)(c3) << 24))
|
|
|
|
#define ANI_FLAG_ICON 0x1
|
|
|
|
typedef struct
|
|
{
|
|
BYTE bWidth;
|
|
BYTE bHeight;
|
|
BYTE bColorCount;
|
|
BYTE bReserved;
|
|
WORD xHotspot;
|
|
WORD yHotspot;
|
|
DWORD dwDIBSize;
|
|
DWORD dwDIBOffset;
|
|
} CURSORICONFILEDIRENTRY;
|
|
|
|
typedef struct
|
|
{
|
|
WORD idReserved;
|
|
WORD idType;
|
|
WORD idCount;
|
|
CURSORICONFILEDIRENTRY idEntries;
|
|
} CURSORICONFILEDIR;
|
|
|
|
typedef struct
|
|
{
|
|
DWORD chunkType; // 'icon'
|
|
DWORD chunkSize;
|
|
|
|
CURSORICONFILEDIR icon_info;
|
|
BITMAPINFOHEADER bmi_header;
|
|
} ANIMICONINFO;
|
|
|
|
typedef struct
|
|
{
|
|
DWORD riffID;
|
|
DWORD riffSizeof;
|
|
|
|
DWORD aconChunkID; // 'ACON'
|
|
DWORD aniChunkID; // 'anih'
|
|
DWORD aniSizeof; // sizeof(ANIHEADER) = 36 bytes
|
|
struct
|
|
{
|
|
DWORD cbSizeof; // sizeof(ANIHEADER) = 36 bytes.
|
|
DWORD frames; // Number of frames in the frame list.
|
|
DWORD steps; // Number of steps in the animation loop.
|
|
DWORD width; // Width
|
|
DWORD height; // Height
|
|
DWORD bpp; // bpp
|
|
DWORD planes; // Not used
|
|
DWORD jifRate; // Default display rate, in jiffies (1/60s)
|
|
DWORD fl; // AF_ICON should be set. AF_SEQUENCE is optional
|
|
} ANIHEADER;
|
|
} RIFFHEADER;
|
|
|
|
#pragma pack(pop)
|
|
|
|
typedef struct CachedCursor
|
|
{
|
|
float scale;
|
|
HCURSOR cursor;
|
|
struct CachedCursor *next;
|
|
} CachedCursor;
|
|
|
|
struct SDL_CursorData
|
|
{
|
|
int hot_x;
|
|
int hot_y;
|
|
int num_frames;
|
|
CachedCursor *cache;
|
|
HCURSOR cursor;
|
|
SDL_CursorFrameInfo frames[1];
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
Uint64 xs[5];
|
|
Uint64 ys[5];
|
|
Sint64 residual[2];
|
|
Uint32 dpiscale;
|
|
Uint32 dpidenom;
|
|
int last_node;
|
|
bool enhanced;
|
|
bool dpiaware;
|
|
} WIN_MouseData;
|
|
|
|
DWORD SDL_last_warp_time = 0;
|
|
HCURSOR SDL_cursor = NULL;
|
|
static SDL_Cursor *SDL_blank_cursor = NULL;
|
|
static WIN_MouseData WIN_system_scale_data;
|
|
|
|
static SDL_Cursor *WIN_CreateCursorAndData(HCURSOR hcursor)
|
|
{
|
|
if (!hcursor) {
|
|
return NULL;
|
|
}
|
|
|
|
SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
|
|
if (!cursor) {
|
|
return NULL;
|
|
}
|
|
|
|
SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data));
|
|
if (!data) {
|
|
SDL_free(cursor);
|
|
return NULL;
|
|
}
|
|
|
|
data->cursor = hcursor;
|
|
cursor->internal = data;
|
|
return cursor;
|
|
}
|
|
|
|
|
|
static bool IsMonochromeSurface(SDL_Surface *surface)
|
|
{
|
|
int x, y;
|
|
Uint8 r, g, b, a;
|
|
|
|
SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
|
|
|
|
for (y = 0; y < surface->h; y++) {
|
|
for (x = 0; x < surface->w; x++) {
|
|
SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a);
|
|
|
|
// Black or white pixel.
|
|
if (!((r == 0x00 && g == 0x00 && b == 0x00) || (r == 0xff && g == 0xff && b == 0xff))) {
|
|
return false;
|
|
}
|
|
|
|
// Transparent or opaque pixel.
|
|
if (!(a == 0x00 || a == 0xff)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static HBITMAP CreateColorBitmap(SDL_Surface *surface)
|
|
{
|
|
HBITMAP bitmap;
|
|
BITMAPINFO bi;
|
|
void *pixels;
|
|
|
|
SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
|
|
|
|
SDL_zero(bi);
|
|
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bi.bmiHeader.biWidth = surface->w;
|
|
bi.bmiHeader.biHeight = -surface->h; // Invert height to make the top-down DIB.
|
|
bi.bmiHeader.biPlanes = 1;
|
|
bi.bmiHeader.biBitCount = 32;
|
|
bi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &pixels, NULL, 0);
|
|
if (!bitmap || !pixels) {
|
|
WIN_SetError("CreateDIBSection()");
|
|
if (bitmap) {
|
|
DeleteObject(bitmap);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SDL_memcpy(pixels, surface->pixels, surface->pitch * surface->h);
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
/* Generate bitmap with a mask and optional monochrome image data.
|
|
*
|
|
* For info on the expected mask format see:
|
|
* https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
|
|
*/
|
|
static HBITMAP CreateMaskBitmap(SDL_Surface *surface, bool is_monochrome)
|
|
{
|
|
HBITMAP bitmap;
|
|
bool isstack;
|
|
void *pixels;
|
|
int x, y;
|
|
Uint8 r, g, b, a;
|
|
Uint8 *dst;
|
|
const int pitch = ((surface->w + 15) & ~15) / 8;
|
|
const int size = pitch * surface->h;
|
|
static const unsigned char masks[] = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
|
|
|
|
SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
|
|
|
|
pixels = SDL_small_alloc(Uint8, size * (is_monochrome ? 2 : 1), &isstack);
|
|
if (!pixels) {
|
|
SDL_OutOfMemory();
|
|
return NULL;
|
|
}
|
|
|
|
dst = (Uint8 *)pixels;
|
|
|
|
// Make the mask completely transparent.
|
|
SDL_memset(dst, 0xff, size);
|
|
if (is_monochrome) {
|
|
SDL_memset(dst + size, 0x00, size);
|
|
}
|
|
|
|
for (y = 0; y < surface->h; y++, dst += pitch) {
|
|
for (x = 0; x < surface->w; x++) {
|
|
SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a);
|
|
|
|
if (a != 0) {
|
|
// Reset bit of an opaque pixel.
|
|
dst[x >> 3] &= ~masks[x & 7];
|
|
}
|
|
|
|
if (is_monochrome && !(r == 0x00 && g == 0x00 && b == 0x00)) {
|
|
// Set bit of white or inverted pixel.
|
|
dst[size + (x >> 3)] |= masks[x & 7];
|
|
}
|
|
}
|
|
}
|
|
|
|
bitmap = CreateBitmap(surface->w, surface->h * (is_monochrome ? 2 : 1), 1, 1, pixels);
|
|
SDL_small_free(pixels, isstack);
|
|
if (!bitmap) {
|
|
WIN_SetError("CreateBitmap()");
|
|
return NULL;
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
static HCURSOR WIN_CreateHCursor(SDL_Surface *surface, int hot_x, int hot_y)
|
|
{
|
|
HCURSOR hcursor = NULL;
|
|
bool is_monochrome = IsMonochromeSurface(surface);
|
|
ICONINFO ii = {
|
|
.fIcon = FALSE,
|
|
.xHotspot = (DWORD)hot_x,
|
|
.yHotspot = (DWORD)hot_y,
|
|
.hbmMask = CreateMaskBitmap(surface, is_monochrome),
|
|
.hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface)
|
|
};
|
|
|
|
if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) {
|
|
SDL_SetError("Couldn't create cursor bitmaps");
|
|
goto cleanup;
|
|
}
|
|
|
|
hcursor = CreateIconIndirect(&ii);
|
|
if (!hcursor) {
|
|
WIN_SetError("CreateIconIndirect failed");
|
|
}
|
|
|
|
cleanup:
|
|
if (ii.hbmMask) {
|
|
DeleteObject(ii.hbmMask);
|
|
}
|
|
if (ii.hbmColor) {
|
|
DeleteObject(ii.hbmColor);
|
|
}
|
|
|
|
return hcursor;
|
|
}
|
|
|
|
/* Windows doesn't have an API to easily create animated cursors from a sequence of images,
|
|
* so we have to build an animated cursor resource file in memory and load it.
|
|
*/
|
|
static HCURSOR WIN_CreateAnimatedCursorInternal(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y, float scale)
|
|
{
|
|
static const double WIN32_JIFFY = 1000.0 / 60.0;
|
|
SDL_Surface *surface = NULL;
|
|
bool use_scaled_surfaces = scale != 1.0f;
|
|
|
|
if (use_scaled_surfaces) {
|
|
surface = SDL_GetSurfaceImage(frames[0].surface, scale);
|
|
} else {
|
|
surface = frames[0].surface;
|
|
}
|
|
if (!surface) {
|
|
return NULL;
|
|
}
|
|
|
|
// Since XP and still as of Win11, Windows cursors have a hard size limit of 256x256.
|
|
if (surface->w > 256 || surface->h > 256) {
|
|
SDL_SetError("Cursor images must be <= 256x256");
|
|
return NULL;
|
|
}
|
|
|
|
const DWORD image_data_size = surface->w * surface->pitch * 2;
|
|
const DWORD total_image_data_size = image_data_size * frame_count;
|
|
const DWORD alloc_size = sizeof(RIFFHEADER) + (sizeof(DWORD) * (5 + frame_count)) + (sizeof(ANIMICONINFO) * frame_count) + total_image_data_size;
|
|
const int w = surface->w;
|
|
const int h = surface->h;
|
|
|
|
hot_x = (int)SDL_round(hot_x * scale);
|
|
hot_y = (int)SDL_round(hot_y * scale);
|
|
|
|
BYTE *membase = SDL_malloc(alloc_size);
|
|
if (!membase) {
|
|
return NULL;
|
|
}
|
|
|
|
RIFFHEADER *riff = (RIFFHEADER *)membase;
|
|
riff->riffID = RIFF_FOURCC('R', 'I', 'F', 'F');
|
|
riff->riffSizeof = alloc_size - (sizeof(DWORD) * 2); // The total size, minus the RIFF header DWORDs.
|
|
riff->aconChunkID = RIFF_FOURCC('A', 'C', 'O', 'N');
|
|
riff->aniChunkID = RIFF_FOURCC('a', 'n', 'i', 'h');
|
|
riff->aniSizeof = sizeof(riff->ANIHEADER);
|
|
riff->ANIHEADER.cbSizeof = sizeof(riff->ANIHEADER);
|
|
riff->ANIHEADER.frames = frame_count;
|
|
riff->ANIHEADER.steps = frame_count;
|
|
riff->ANIHEADER.width = w;
|
|
riff->ANIHEADER.height = h;
|
|
riff->ANIHEADER.bpp = 32;
|
|
riff->ANIHEADER.planes = 1;
|
|
riff->ANIHEADER.jifRate = 1;
|
|
riff->ANIHEADER.fl = ANI_FLAG_ICON;
|
|
|
|
DWORD *dwptr = (DWORD *)(membase + sizeof(*riff));
|
|
|
|
// Rate chunk
|
|
*dwptr++ = RIFF_FOURCC('r', 'a', 't', 'e');
|
|
*dwptr++ = sizeof(DWORD) * frame_count;
|
|
for (int i = 0; i < frame_count; ++i) {
|
|
// Animated Win32 cursors are in jiffy units, and one jiffy is 1/60 of a second.
|
|
*dwptr++ = frames[i].duration ? SDL_lround(frames[i].duration / WIN32_JIFFY) : 0xFFFFFFFF;
|
|
}
|
|
|
|
// Frame list chunk
|
|
*dwptr++ = RIFF_FOURCC('L', 'I', 'S', 'T');
|
|
*dwptr++ = (sizeof(ANIMICONINFO) * frame_count) + total_image_data_size + sizeof(DWORD);
|
|
*dwptr++ = RIFF_FOURCC('f', 'r', 'a', 'm');
|
|
|
|
BYTE *icon_data = (BYTE *)dwptr;
|
|
|
|
for (int i = 0; i < frame_count; ++i) {
|
|
if (!surface) {
|
|
if (use_scaled_surfaces) {
|
|
surface = SDL_GetSurfaceImage(frames[i].surface, scale);
|
|
if (!surface) {
|
|
SDL_free(membase);
|
|
return NULL;
|
|
}
|
|
}
|
|
} else {
|
|
surface = frames[i].surface;
|
|
}
|
|
|
|
/* Cursor data is double height (DIB and mask), and has a max width and height of 256 (represented by a value of 0).
|
|
* https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
|
|
*/
|
|
ANIMICONINFO *icon_info = (ANIMICONINFO *)icon_data;
|
|
icon_info->chunkType = RIFF_FOURCC('i', 'c', 'o', 'n');
|
|
icon_info->chunkSize = sizeof(ANIMICONINFO) + image_data_size - (sizeof(DWORD) * 2);
|
|
icon_info->icon_info.idReserved = 0;
|
|
icon_info->icon_info.idType = 2;
|
|
icon_info->icon_info.idCount = 1;
|
|
icon_info->icon_info.idEntries.bWidth = w < 256 ? w : 0; // 0 means a width of 256
|
|
icon_info->icon_info.idEntries.bHeight = h < 256 ? h : 0; // 0 means a height of 256
|
|
icon_info->icon_info.idEntries.bColorCount = 0;
|
|
icon_info->icon_info.idEntries.bReserved = 0;
|
|
icon_info->icon_info.idEntries.xHotspot = hot_x;
|
|
icon_info->icon_info.idEntries.yHotspot = hot_y;
|
|
icon_info->icon_info.idEntries.dwDIBSize = image_data_size;
|
|
icon_info->icon_info.idEntries.dwDIBOffset = offsetof(ANIMICONINFO, bmi_header) - (sizeof(DWORD) * 2);
|
|
icon_info->bmi_header.biSize = sizeof(BITMAPINFOHEADER);
|
|
icon_info->bmi_header.biWidth = w;
|
|
icon_info->bmi_header.biHeight = h * 2;
|
|
icon_info->bmi_header.biPlanes = 1;
|
|
icon_info->bmi_header.biBitCount = 32;
|
|
icon_info->bmi_header.biCompression = BI_RGB;
|
|
icon_info->bmi_header.biSizeImage = 0;
|
|
icon_info->bmi_header.biXPelsPerMeter = 0;
|
|
icon_info->bmi_header.biYPelsPerMeter = 0;
|
|
icon_info->bmi_header.biClrUsed = 0;
|
|
icon_info->bmi_header.biClrImportant = 0;
|
|
|
|
icon_data += sizeof(ANIMICONINFO);
|
|
|
|
// Cursor DIB images are stored bottom-up and double height: the bitmap, and the mask
|
|
const Uint8 *pix = frames[i].surface->pixels;
|
|
pix += (frames[i].surface->h - 1) * frames[i].surface->pitch;
|
|
for (int j = 0; j < frames[i].surface->h; j++) {
|
|
SDL_memcpy(icon_data, pix, frames[i].surface->pitch);
|
|
pix -= frames[i].surface->pitch;
|
|
icon_data += frames[i].surface->pitch;
|
|
}
|
|
|
|
// Should we generate mask data here?
|
|
icon_data += (image_data_size / 2);
|
|
|
|
if (use_scaled_surfaces) {
|
|
SDL_DestroySurface(surface);
|
|
}
|
|
surface = NULL;
|
|
}
|
|
|
|
HCURSOR hcursor = (HCURSOR)CreateIconFromResource(membase, alloc_size, FALSE, 0x00030000);
|
|
SDL_free(membase);
|
|
|
|
return hcursor;
|
|
}
|
|
|
|
static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
|
|
{
|
|
if (!SDL_SurfaceHasAlternateImages(surface)) {
|
|
HCURSOR hcursor = WIN_CreateHCursor(surface, hot_x, hot_y);
|
|
if (!hcursor) {
|
|
return NULL;
|
|
}
|
|
return WIN_CreateCursorAndData(hcursor);
|
|
}
|
|
|
|
// Dynamically generate cursors at the appropriate DPI
|
|
SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
|
|
if (cursor) {
|
|
SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data));
|
|
if (!data) {
|
|
SDL_free(cursor);
|
|
return NULL;
|
|
}
|
|
data->hot_x = hot_x;
|
|
data->hot_y = hot_y;
|
|
data->num_frames = 1;
|
|
data->frames[0].surface = surface;
|
|
++surface->refcount;
|
|
cursor->internal = data;
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
static SDL_Cursor *WIN_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y)
|
|
{
|
|
if (!SDL_SurfaceHasAlternateImages(frames[0].surface)) {
|
|
HCURSOR hcursor = WIN_CreateAnimatedCursorInternal(frames, frame_count, hot_x, hot_y, 1.0f);
|
|
if (!hcursor) {
|
|
return NULL;
|
|
}
|
|
return WIN_CreateCursorAndData(hcursor);
|
|
}
|
|
|
|
// Dynamically generate cursors at the appropriate DPI
|
|
SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
|
|
if (cursor) {
|
|
SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data) + (sizeof(SDL_CursorFrameInfo) * (frame_count - 1)));
|
|
if (!data) {
|
|
SDL_free(cursor);
|
|
return NULL;
|
|
}
|
|
data->hot_x = hot_x;
|
|
data->hot_y = hot_y;
|
|
data->num_frames = frame_count;
|
|
for (int i = 0; i < frame_count; ++i) {
|
|
data->frames[i].surface = frames[i].surface;
|
|
data->frames[i].duration = frames[i].duration;
|
|
++frames[i].surface->refcount;
|
|
}
|
|
cursor->internal = data;
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
static SDL_Cursor *WIN_CreateBlankCursor(void)
|
|
{
|
|
SDL_Cursor *cursor = NULL;
|
|
SDL_Surface *surface = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_ARGB8888);
|
|
if (surface) {
|
|
cursor = WIN_CreateCursor(surface, 0, 0);
|
|
SDL_DestroySurface(surface);
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
static SDL_Cursor *WIN_CreateSystemCursor(SDL_SystemCursor id)
|
|
{
|
|
LPCTSTR name;
|
|
|
|
switch (id) {
|
|
default:
|
|
SDL_assert(!"Unknown system cursor ID");
|
|
return NULL;
|
|
case SDL_SYSTEM_CURSOR_DEFAULT:
|
|
name = IDC_ARROW;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_TEXT:
|
|
name = IDC_IBEAM;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_WAIT:
|
|
name = IDC_WAIT;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_CROSSHAIR:
|
|
name = IDC_CROSS;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_PROGRESS:
|
|
name = IDC_APPSTARTING;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_NWSE_RESIZE:
|
|
name = IDC_SIZENWSE;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_NESW_RESIZE:
|
|
name = IDC_SIZENESW;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_EW_RESIZE:
|
|
name = IDC_SIZEWE;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_NS_RESIZE:
|
|
name = IDC_SIZENS;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_MOVE:
|
|
name = IDC_SIZEALL;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_NOT_ALLOWED:
|
|
name = IDC_NO;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_POINTER:
|
|
name = IDC_HAND;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_NW_RESIZE:
|
|
name = IDC_SIZENWSE;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_N_RESIZE:
|
|
name = IDC_SIZENS;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_NE_RESIZE:
|
|
name = IDC_SIZENESW;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_E_RESIZE:
|
|
name = IDC_SIZEWE;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_SE_RESIZE:
|
|
name = IDC_SIZENWSE;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_S_RESIZE:
|
|
name = IDC_SIZENS;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_SW_RESIZE:
|
|
name = IDC_SIZENESW;
|
|
break;
|
|
case SDL_SYSTEM_CURSOR_W_RESIZE:
|
|
name = IDC_SIZEWE;
|
|
break;
|
|
}
|
|
return WIN_CreateCursorAndData(LoadCursor(NULL, name));
|
|
}
|
|
|
|
static SDL_Cursor *WIN_CreateDefaultCursor(void)
|
|
{
|
|
SDL_SystemCursor id = SDL_GetDefaultSystemCursor();
|
|
return WIN_CreateSystemCursor(id);
|
|
}
|
|
|
|
static void WIN_FreeCursor(SDL_Cursor *cursor)
|
|
{
|
|
SDL_CursorData *data = cursor->internal;
|
|
|
|
for (int i = 0; i < data->num_frames; ++i) {
|
|
SDL_DestroySurface(data->frames[i].surface);
|
|
}
|
|
while (data->cache) {
|
|
CachedCursor *entry = data->cache;
|
|
data->cache = entry->next;
|
|
if (entry->cursor) {
|
|
DestroyCursor(entry->cursor);
|
|
}
|
|
SDL_free(entry);
|
|
}
|
|
if (data->cursor) {
|
|
DestroyCursor(data->cursor);
|
|
}
|
|
SDL_free(data);
|
|
SDL_free(cursor);
|
|
}
|
|
|
|
static HCURSOR GetCachedCursor(SDL_Cursor *cursor)
|
|
{
|
|
SDL_CursorData *data = cursor->internal;
|
|
|
|
SDL_Window *focus = SDL_GetMouseFocus();
|
|
if (!focus) {
|
|
return NULL;
|
|
}
|
|
|
|
float scale = SDL_GetDisplayContentScale(SDL_GetDisplayForWindow(focus));
|
|
for (CachedCursor *entry = data->cache; entry; entry = entry->next) {
|
|
if (scale == entry->scale) {
|
|
return entry->cursor;
|
|
}
|
|
}
|
|
|
|
CachedCursor *entry = NULL;
|
|
HCURSOR hcursor = NULL;
|
|
|
|
// Need to create a cursor for this content scale
|
|
if (data->num_frames == 1) {
|
|
SDL_Surface *surface = NULL;
|
|
|
|
surface = SDL_GetSurfaceImage(data->frames[0].surface, scale);
|
|
if (!surface) {
|
|
goto error;
|
|
}
|
|
|
|
int hot_x = (int)SDL_round(data->hot_x * scale);
|
|
int hot_y = (int)SDL_round(data->hot_y * scale);
|
|
hcursor = WIN_CreateHCursor(surface, hot_x, hot_y);
|
|
SDL_DestroySurface(surface);
|
|
if (!hcursor) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
hcursor = WIN_CreateAnimatedCursorInternal(data->frames, data->num_frames, data->hot_x, data->hot_y, scale);
|
|
}
|
|
|
|
entry = (CachedCursor *)SDL_malloc(sizeof(*entry));
|
|
if (!entry) {
|
|
goto error;
|
|
}
|
|
entry->cursor = hcursor;
|
|
entry->scale = scale;
|
|
entry->next = data->cache;
|
|
data->cache = entry;
|
|
|
|
return hcursor;
|
|
|
|
error:
|
|
if (hcursor) {
|
|
DestroyCursor(hcursor);
|
|
}
|
|
SDL_free(entry);
|
|
return NULL;
|
|
}
|
|
|
|
static bool WIN_ShowCursor(SDL_Cursor *cursor)
|
|
{
|
|
if (!cursor) {
|
|
if (GetSystemMetrics(SM_REMOTESESSION)) {
|
|
// Use a blank cursor so we continue to get relative motion over RDP
|
|
cursor = SDL_blank_cursor;
|
|
}
|
|
}
|
|
if (cursor) {
|
|
if (cursor->internal->num_frames) {
|
|
SDL_cursor = GetCachedCursor(cursor);
|
|
} else {
|
|
SDL_cursor = cursor->internal->cursor;
|
|
}
|
|
} else {
|
|
SDL_cursor = NULL;
|
|
}
|
|
if (SDL_GetMouseFocus() != NULL) {
|
|
SetCursor(SDL_cursor);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WIN_SetCursorPos(int x, int y)
|
|
{
|
|
// We need to jitter the value because otherwise Windows will occasionally inexplicably ignore the SetCursorPos() or SendInput()
|
|
SetCursorPos(x, y);
|
|
SetCursorPos(x + 1, y);
|
|
SetCursorPos(x, y);
|
|
|
|
// Flush any mouse motion prior to or associated with this warp
|
|
#ifdef _MSC_VER // We explicitly want to use GetTickCount(), not GetTickCount64()
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 28159)
|
|
#endif
|
|
SDL_last_warp_time = GetTickCount();
|
|
if (!SDL_last_warp_time) {
|
|
SDL_last_warp_time = 1;
|
|
}
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
}
|
|
|
|
static bool WIN_WarpMouse(SDL_Window *window, float x, float y)
|
|
{
|
|
SDL_WindowData *data = window->internal;
|
|
HWND hwnd = data->hwnd;
|
|
POINT pt;
|
|
|
|
// Don't warp the mouse while we're doing a modal interaction
|
|
if (data->in_title_click || data->focus_click_pending) {
|
|
return true;
|
|
}
|
|
|
|
pt.x = (int)SDL_roundf(x);
|
|
pt.y = (int)SDL_roundf(y);
|
|
ClientToScreen(hwnd, &pt);
|
|
WIN_SetCursorPos(pt.x, pt.y);
|
|
|
|
// Send the exact mouse motion associated with this warp
|
|
SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y);
|
|
return true;
|
|
}
|
|
|
|
static bool WIN_WarpMouseGlobal(float x, float y)
|
|
{
|
|
POINT pt;
|
|
|
|
pt.x = (int)SDL_roundf(x);
|
|
pt.y = (int)SDL_roundf(y);
|
|
SetCursorPos(pt.x, pt.y);
|
|
return true;
|
|
}
|
|
|
|
static bool WIN_SetRelativeMouseMode(bool enabled)
|
|
{
|
|
return WIN_SetRawMouseEnabled(SDL_GetVideoDevice(), enabled);
|
|
}
|
|
|
|
static bool WIN_CaptureMouse(SDL_Window *window)
|
|
{
|
|
if (window) {
|
|
SDL_WindowData *data = window->internal;
|
|
SetCapture(data->hwnd);
|
|
} else {
|
|
SDL_Window *focus_window = SDL_GetMouseFocus();
|
|
|
|
if (focus_window) {
|
|
SDL_WindowData *data = focus_window->internal;
|
|
if (!data->mouse_tracked) {
|
|
SDL_SetMouseFocus(NULL);
|
|
}
|
|
}
|
|
ReleaseCapture();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static SDL_MouseButtonFlags WIN_GetGlobalMouseState(float *x, float *y)
|
|
{
|
|
SDL_MouseButtonFlags result = 0;
|
|
POINT pt = { 0, 0 };
|
|
bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
|
|
|
|
GetCursorPos(&pt);
|
|
*x = (float)pt.x;
|
|
*y = (float)pt.y;
|
|
|
|
result |= GetAsyncKeyState(!swapButtons ? VK_LBUTTON : VK_RBUTTON) & 0x8000 ? SDL_BUTTON_LMASK : 0;
|
|
result |= GetAsyncKeyState(!swapButtons ? VK_RBUTTON : VK_LBUTTON) & 0x8000 ? SDL_BUTTON_RMASK : 0;
|
|
result |= GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_BUTTON_MMASK : 0;
|
|
result |= GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_BUTTON_X1MASK : 0;
|
|
result |= GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_BUTTON_X2MASK : 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
static void WIN_ApplySystemScale(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y)
|
|
{
|
|
if (!internal) {
|
|
return;
|
|
}
|
|
WIN_MouseData *data = (WIN_MouseData *)internal;
|
|
|
|
SDL_VideoDisplay *display = window ? SDL_GetVideoDisplayForWindow(window) : SDL_GetVideoDisplay(SDL_GetPrimaryDisplay());
|
|
|
|
Sint64 ix = (Sint64)*x * 65536;
|
|
Sint64 iy = (Sint64)*y * 65536;
|
|
Uint32 dpi = display ? (Uint32)(display->content_scale * USER_DEFAULT_SCREEN_DPI) : USER_DEFAULT_SCREEN_DPI;
|
|
|
|
if (!data->enhanced) { // early return if flat scale
|
|
dpi = data->dpiscale * (data->dpiaware ? dpi : USER_DEFAULT_SCREEN_DPI);
|
|
ix *= dpi;
|
|
iy *= dpi;
|
|
ix /= USER_DEFAULT_SCREEN_DPI;
|
|
iy /= USER_DEFAULT_SCREEN_DPI;
|
|
ix /= 32;
|
|
iy /= 32;
|
|
// data->residual[0] += ix;
|
|
// data->residual[1] += iy;
|
|
// ix = 65536 * (data->residual[0] / 65536);
|
|
// iy = 65536 * (data->residual[1] / 65536);
|
|
// data->residual[0] -= ix;
|
|
// data->residual[1] -= iy;
|
|
*x = (float)ix / 65536.0f;
|
|
*y = (float)iy / 65536.0f;
|
|
return;
|
|
}
|
|
|
|
Uint64 *xs = data->xs;
|
|
Uint64 *ys = data->ys;
|
|
Uint64 absx = SDL_abs(ix);
|
|
Uint64 absy = SDL_abs(iy);
|
|
Uint64 speed = SDL_min(absx, absy) + (SDL_max(absx, absy) << 1); // super cursed approximation used by Windows
|
|
if (speed == 0) {
|
|
return;
|
|
}
|
|
|
|
int i, j, k;
|
|
for (i = 1; i < 5; i++) {
|
|
j = i;
|
|
if (speed < xs[j]) {
|
|
break;
|
|
}
|
|
}
|
|
i -= 1;
|
|
j -= 1;
|
|
k = data->last_node;
|
|
data->last_node = j;
|
|
|
|
Uint32 denom = data->dpidenom;
|
|
Sint64 scale = 0;
|
|
Sint64 xdiff = xs[j+1] - xs[j];
|
|
Sint64 ydiff = ys[j+1] - ys[j];
|
|
if (xdiff != 0) {
|
|
Sint64 slope = ydiff / xdiff;
|
|
Sint64 inter = slope * xs[i] - ys[i];
|
|
scale += slope - inter / speed;
|
|
}
|
|
|
|
if (j > k) {
|
|
denom <<= 1;
|
|
xdiff = xs[k+1] - xs[k];
|
|
ydiff = ys[k+1] - ys[k];
|
|
if (xdiff != 0) {
|
|
Sint64 slope = ydiff / xdiff;
|
|
Sint64 inter = slope * xs[k] - ys[k];
|
|
scale += slope - inter / speed;
|
|
}
|
|
}
|
|
|
|
scale *= dpi;
|
|
ix *= scale;
|
|
iy *= scale;
|
|
ix /= denom;
|
|
iy /= denom;
|
|
// data->residual[0] += ix;
|
|
// data->residual[1] += iy;
|
|
// ix = 65536 * (data->residual[0] / 65536);
|
|
// iy = 65536 * (data->residual[1] / 65536);
|
|
// data->residual[0] -= ix;
|
|
// data->residual[1] -= iy;
|
|
*x = (float)ix / 65536.0f;
|
|
*y = (float)iy / 65536.0f;
|
|
}
|
|
|
|
void WIN_InitMouse(SDL_VideoDevice *_this)
|
|
{
|
|
SDL_Mouse *mouse = SDL_GetMouse();
|
|
|
|
mouse->CreateCursor = WIN_CreateCursor;
|
|
mouse->CreateAnimatedCursor = WIN_CreateAnimatedCursor;
|
|
mouse->CreateSystemCursor = WIN_CreateSystemCursor;
|
|
mouse->ShowCursor = WIN_ShowCursor;
|
|
mouse->FreeCursor = WIN_FreeCursor;
|
|
mouse->WarpMouse = WIN_WarpMouse;
|
|
mouse->WarpMouseGlobal = WIN_WarpMouseGlobal;
|
|
mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode;
|
|
mouse->CaptureMouse = WIN_CaptureMouse;
|
|
mouse->GetGlobalMouseState = WIN_GetGlobalMouseState;
|
|
mouse->ApplySystemScale = WIN_ApplySystemScale;
|
|
mouse->system_scale_data = &WIN_system_scale_data;
|
|
|
|
SDL_SetDefaultCursor(WIN_CreateDefaultCursor());
|
|
|
|
SDL_blank_cursor = WIN_CreateBlankCursor();
|
|
|
|
WIN_UpdateMouseSystemScale();
|
|
}
|
|
|
|
void WIN_QuitMouse(SDL_VideoDevice *_this)
|
|
{
|
|
if (SDL_blank_cursor) {
|
|
WIN_FreeCursor(SDL_blank_cursor);
|
|
SDL_blank_cursor = NULL;
|
|
}
|
|
}
|
|
|
|
static void ReadMouseCurve(int v, Uint64 xs[5], Uint64 ys[5])
|
|
{
|
|
bool win8 = WIN_IsWindows8OrGreater();
|
|
DWORD xbuff[10] = {
|
|
0x00000000, 0,
|
|
0x00006e15, 0,
|
|
0x00014000, 0,
|
|
0x0003dc29, 0,
|
|
0x00280000, 0
|
|
};
|
|
DWORD ybuff[10] = {
|
|
0x00000000, 0,
|
|
win8 ? 0x000111fd : 0x00015eb8, 0,
|
|
win8 ? 0x00042400 : 0x00054ccd, 0,
|
|
win8 ? 0x0012fc00 : 0x00184ccd, 0,
|
|
win8 ? 0x01bbc000 : 0x02380000, 0
|
|
};
|
|
DWORD xsize = sizeof(xbuff);
|
|
DWORD ysize = sizeof(ybuff);
|
|
HKEY open_handle;
|
|
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Mouse", 0, KEY_READ, &open_handle) == ERROR_SUCCESS) {
|
|
RegQueryValueExW(open_handle, L"SmoothMouseXCurve", NULL, NULL, (BYTE*)xbuff, &xsize);
|
|
RegQueryValueExW(open_handle, L"SmoothMouseYCurve", NULL, NULL, (BYTE*)ybuff, &ysize);
|
|
RegCloseKey(open_handle);
|
|
}
|
|
xs[0] = 0; // first node must always be origin
|
|
ys[0] = 0; // first node must always be origin
|
|
int i;
|
|
for (i = 1; i < 5; i++) {
|
|
xs[i] = (7 * (Uint64)xbuff[i * 2]);
|
|
ys[i] = (v * (Uint64)ybuff[i * 2]) << 17;
|
|
}
|
|
}
|
|
|
|
void WIN_UpdateMouseSystemScale(void)
|
|
{
|
|
SDL_Mouse *mouse = SDL_GetMouse();
|
|
|
|
if (mouse->ApplySystemScale == WIN_ApplySystemScale) {
|
|
mouse->system_scale_data = &WIN_system_scale_data;
|
|
}
|
|
|
|
// always reinitialize to valid defaults, whether fetch was successful or not.
|
|
WIN_MouseData *data = &WIN_system_scale_data;
|
|
data->residual[0] = 0;
|
|
data->residual[1] = 0;
|
|
data->dpiscale = 32;
|
|
data->dpidenom = (10 * (WIN_IsWindows8OrGreater() ? 120 : 150)) << 16;
|
|
data->dpiaware = WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice());
|
|
data->enhanced = false;
|
|
|
|
int v = 10;
|
|
if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &v, 0)) {
|
|
v = SDL_max(1, SDL_min(v, 20));
|
|
data->dpiscale = SDL_max(SDL_max(v, (v - 2) * 4), (v - 6) * 8);
|
|
}
|
|
|
|
int params[3];
|
|
if (SystemParametersInfo(SPI_GETMOUSE, 0, ¶ms, 0)) {
|
|
data->enhanced = params[2] ? true : false;
|
|
if (params[2]) {
|
|
ReadMouseCurve(v, data->xs, data->ys);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // SDL_VIDEO_DRIVER_WINDOWS
|