|
|
@@ -31,6 +31,66 @@
|
|
|
#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
|
|
|
{
|
|
|
@@ -41,11 +101,12 @@ typedef struct CachedCursor
|
|
|
|
|
|
struct SDL_CursorData
|
|
|
{
|
|
|
- SDL_Surface *surface;
|
|
|
int hot_x;
|
|
|
int hot_y;
|
|
|
+ int num_frames;
|
|
|
CachedCursor *cache;
|
|
|
HCURSOR cursor;
|
|
|
+ SDL_CursorFrameInfo frames[1];
|
|
|
};
|
|
|
|
|
|
typedef struct
|
|
|
@@ -207,12 +268,12 @@ 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,
|
|
|
+ ICONINFO ii = {
|
|
|
+ .fIcon = FALSE,
|
|
|
+ .xHotspot = (DWORD)hot_x,
|
|
|
.yHotspot = (DWORD)hot_y,
|
|
|
.hbmMask = CreateMaskBitmap(surface, is_monochrome),
|
|
|
- .hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface)
|
|
|
+ .hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface)
|
|
|
};
|
|
|
|
|
|
if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) {
|
|
|
@@ -236,6 +297,141 @@ cleanup:
|
|
|
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;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Since XP and still as of Win11, Windows cursors have a hard size limit of 256x256.
|
|
|
+ if (!surface || surface->w > 256 || surface->h > 256) {
|
|
|
+ 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)) {
|
|
|
@@ -256,13 +452,45 @@ static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
|
|
|
}
|
|
|
data->hot_x = hot_x;
|
|
|
data->hot_y = hot_y;
|
|
|
- data->surface = surface;
|
|
|
+ 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 = 1;
|
|
|
+ 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;
|
|
|
@@ -356,8 +584,8 @@ static void WIN_FreeCursor(SDL_Cursor *cursor)
|
|
|
{
|
|
|
SDL_CursorData *data = cursor->internal;
|
|
|
|
|
|
- if (data->surface) {
|
|
|
- SDL_DestroySurface(data->surface);
|
|
|
+ for (int i = 0; i < data->num_frames; ++i) {
|
|
|
+ SDL_DestroySurface(data->frames[i].surface);
|
|
|
}
|
|
|
while (data->cache) {
|
|
|
CachedCursor *entry = data->cache;
|
|
|
@@ -390,21 +618,27 @@ static HCURSOR GetCachedCursor(SDL_Cursor *cursor)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Need to create a cursor for this content scale
|
|
|
- SDL_Surface *surface = NULL;
|
|
|
- HCURSOR hcursor = NULL;
|
|
|
CachedCursor *entry = NULL;
|
|
|
+ HCURSOR hcursor = NULL;
|
|
|
|
|
|
- surface = SDL_GetSurfaceImage(data->surface, scale);
|
|
|
- if (!surface) {
|
|
|
- goto error;
|
|
|
- }
|
|
|
+ // Need to create a cursor for this content scale
|
|
|
+ if (data->num_frames == 1) {
|
|
|
+ SDL_Surface *surface = NULL;
|
|
|
|
|
|
- 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);
|
|
|
- if (!hcursor) {
|
|
|
- goto error;
|
|
|
+ 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));
|
|
|
@@ -416,14 +650,9 @@ static HCURSOR GetCachedCursor(SDL_Cursor *cursor)
|
|
|
entry->next = data->cache;
|
|
|
data->cache = entry;
|
|
|
|
|
|
- SDL_DestroySurface(surface);
|
|
|
-
|
|
|
return hcursor;
|
|
|
|
|
|
error:
|
|
|
- if (surface) {
|
|
|
- SDL_DestroySurface(surface);
|
|
|
- }
|
|
|
if (hcursor) {
|
|
|
DestroyCursor(hcursor);
|
|
|
}
|
|
|
@@ -440,7 +669,7 @@ static bool WIN_ShowCursor(SDL_Cursor *cursor)
|
|
|
}
|
|
|
}
|
|
|
if (cursor) {
|
|
|
- if (cursor->internal->surface) {
|
|
|
+ if (cursor->internal->num_frames) {
|
|
|
SDL_cursor = GetCachedCursor(cursor);
|
|
|
} else {
|
|
|
SDL_cursor = cursor->internal->cursor;
|
|
|
@@ -644,6 +873,7 @@ 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;
|