From fc2759b405d105df3c20ae8513e47658f44ecf3a Mon Sep 17 00:00:00 2001
From: Andrew Riedi <andrewriedi@gmail.com>
Date: Mon, 16 Feb 2009 16:49:04 -0800
Subject: [PATCH] user32 / winex11.drv: Implement .ani cursors more completely.

Based on patches by Henri Verbeet.
---
 dlls/user32/cursoricon.c |  234 +++++++++++++++++++++++++++++++++++++---------
 dlls/winex11.drv/mouse.c |   53 ++++++++---
 2 files changed, 229 insertions(+), 58 deletions(-)

diff --git a/dlls/user32/cursoricon.c b/dlls/user32/cursoricon.c
index d414bf0..388d7ab 100644
--- a/dlls/user32/cursoricon.c
+++ b/dlls/user32/cursoricon.c
@@ -962,14 +962,11 @@ static CURSORICONFILEDIRENTRY *CURSORICON_FindBestIconFile( CURSORICONFILEDIR *d
     return &dir->idEntries[n];
 }
 
-static HICON CURSORICON_CreateIconFromBMI( BITMAPINFO *bmi,
-					   POINT16 hotspot, BOOL bIcon,
-					   DWORD dwVersion,
-					   INT width, INT height,
-					   UINT cFlag )
+static CURSORICONINFO *CURSORICON_CreateFrameFromBMI(BITMAPINFO *bmi,
+    POINT16 hotspot, BOOL bIcon, DWORD dwVersion, INT width, INT height,
+    UINT cFlag, SIZE_T *frame_size)
 {
-    HICON cursor = 0;
-    struct cursor *cursor_data;
+    CURSORICONINFO *info;
     static HDC hdcMem;
     int sizeAnd, sizeXor;
     HBITMAP hAndBits = 0, hXorBits = 0; /* error condition for later */
@@ -1125,15 +1122,10 @@ static HICON CURSORICON_CreateIconFromBMI( BITMAPINFO *bmi,
     sizeXor = bmpXor.bmHeight * bmpXor.bmWidthBytes;
     sizeAnd = bmpAnd.bmHeight * bmpAnd.bmWidthBytes;
 
-    cursor_data = HeapAlloc(GetProcessHeap(), 0,
-        sizeof(struct cursor) + sizeof(CURSORICONINFO) + sizeXor + sizeAnd);
-    if (cursor_data)
+    *frame_size = sizeof(CURSORICONINFO) + sizeXor + sizeAnd;
+    info = HeapAlloc(GetProcessHeap(), 0, *frame_size);
+    if (info)
     {
-        CURSORICONINFO *info = (CURSORICONINFO *) (cursor_data + 1);
-
-        cursor_data->num_frames = 1;
-        cursor_data->delay = 0;
-
         info->ptHotSpot.x   = hotspot.x;
         info->ptHotSpot.y   = hotspot.y;
         info->nWidth        = bmpXor.bmWidth;
@@ -1145,14 +1137,45 @@ static HICON CURSORICON_CreateIconFromBMI( BITMAPINFO *bmi,
         /* Transfer the bitmap bits to the CURSORICONINFO structure */
         GetBitmapBits( hAndBits, sizeAnd, (char *)(info + 1) );
         GetBitmapBits( hXorBits, sizeXor, (char *)(info + 1) + sizeAnd );
+    }
+
+    DeleteObject( hAndBits );
+    DeleteObject( hXorBits );
+
+    return info;
+}
+
+static HICON CURSORICON_CreateIconFromBMI(BITMAPINFO *bmi, POINT16 hotspot,
+    BOOL bIcon, DWORD dwVersion, INT width, INT height, UINT cFlag)
+{
+    HICON cursor = 0;
+    struct cursor *cursor_data;
+    CURSORICONINFO *frame;
+    SIZE_T frame_size = 0;
+
+    frame = CURSORICON_CreateFrameFromBMI(bmi, hotspot, bIcon, dwVersion,
+        width, height, cFlag, &frame_size);
+    if (!frame)
+    {
+        ERR("Unable to retrieve frame.\n");
+        return 0;
+    }
+
+    cursor_data = HeapAlloc(GetProcessHeap(), 0,
+        sizeof(struct cursor) + frame_size);
+    if (cursor_data)
+    {
+        cursor_data->num_frames = 1;
+        cursor_data->delay = 0;
+
+        memcpy(cursor_data + 1, frame, frame_size);
 
         /* Make 32-bit cursor. */
         cursor = create_cursor(0, cursor_data,
-            sizeof(struct cursor) + sizeof(CURSORICONINFO) + sizeXor + sizeAnd);
+            sizeof(struct cursor) + frame_size);
     }
 
-    DeleteObject( hAndBits );
-    DeleteObject( hXorBits );
+    HeapFree(GetProcessHeap(), 0, frame);
 
     return cursor;
 }
@@ -1192,6 +1215,12 @@ typedef struct {
     const unsigned char   *data;
 } riff_chunk_t;
 
+typedef struct
+{
+    CURSORICONINFO *frame;
+    SIZE_T size;
+} frame_map_t;
+
 static void dump_ani_header( const ani_header *header )
 {
     TRACE("     header size: %d\n", header->header_size);
@@ -1267,16 +1296,23 @@ static void riff_find_chunk( DWORD chunk_id, DWORD chunk_type, const riff_chunk_
 static HCURSOR CURSORICON_CreateIconFromANI( const LPBYTE bits, DWORD bits_size,
     INT width, INT height, INT colors )
 {
-    HCURSOR cursor;
+    HCURSOR cursor = 0;
+    struct cursor *cursor_data;
     ani_header header = {0};
     LPBYTE frame_bits = 0;
-    POINT16 hotspot;
-    CURSORICONFILEDIRENTRY *entry;
+    SIZE_T frames_size = 0, frame_bits_size = 0;
+    WORD max_count = 0;
+    frame_map_t *frame_map;
+    UINT i;
+    CURSORICONFILEDIR *dir = 0;
+    LONG *seq;
 
     riff_chunk_t root_chunk = { bits_size, bits };
     riff_chunk_t ACON_chunk = {0};
     riff_chunk_t anih_chunk = {0};
+    riff_chunk_t seq_chunk = {0};
     riff_chunk_t fram_chunk = {0};
+    const unsigned char *icon_chunk;
     const unsigned char *icon_data;
 
     TRACE("bits %p, bits_size %d\n", bits, bits_size);
@@ -1299,6 +1335,27 @@ static HCURSOR CURSORICON_CreateIconFromANI( const LPBYTE bits, DWORD bits_size,
     memcpy( &header, anih_chunk.data, sizeof(header) );
     dump_ani_header( &header );
 
+    riff_find_chunk( ANI_seq__ID, 0, &ACON_chunk, &seq_chunk );
+    if (!seq_chunk.data)
+    {
+        TRACE("Sequence list not found.\n");
+        seq = NULL;
+    }
+    else
+    {
+        /* Sanity check. */
+        if (header.num_steps == seq_chunk.data_size / sizeof(LONG))
+        {
+            TRACE("Using sequence list.\n");
+            seq = (LONG *) seq_chunk.data;
+        }
+        else
+        {
+            ERR("Recieved incorrect sequence data, ignoring sequence list.\n");
+            seq = NULL;
+        }
+    }
+
     riff_find_chunk( ANI_fram_ID, ANI_LIST_ID, &ACON_chunk, &fram_chunk );
     if (!fram_chunk.data)
     {
@@ -1306,36 +1363,127 @@ static HCURSOR CURSORICON_CreateIconFromANI( const LPBYTE bits, DWORD bits_size,
         return 0;
     }
 
-    /* FIXME: For now, just load the first frame.  Before we can load all the
-     * frames, we need to write the needed code in wineserver, etc. to handle
-     * cursors.  Once this code is written, we can extend it to support .ani
-     * cursors and then update user32 and winex11.drv to load all frames.
-     *
-     * Hopefully this will at least make some games (C&C3, etc.) more playable
-     * in the meantime.
-     */
-    FIXME("Loading all frames for .ani cursors not implemented.\n");
-    icon_data = fram_chunk.data + (2 * sizeof(DWORD));
+    /* Keep track of the individual frames. */
+    frame_map = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
+        sizeof(frame_map_t) * header.num_frames);
+    if (!frame_map)
+    {
+        ERR("Failed to allocate frame_map.\n");
+        return 0;
+    }
+
+    icon_chunk = fram_chunk.data;
+    icon_data = icon_chunk + (2 * sizeof(DWORD));
+
+    for (i = 0; i < header.num_frames; i++)
+    {
+        CURSORICONFILEDIRENTRY *entry;
+        POINT16 hotspot;
+        WORD count;
+        DWORD chunk_size = *(DWORD *)(icon_chunk + sizeof(DWORD));
+
+        /* Read icon count, skip magic. */
+        memcpy(&count, icon_data + sizeof(DWORD), sizeof(WORD));
+
+        /* There's a decent chance the amount of entries will be the same for
+         * each icon. */
+        if (count > max_count)
+        {
+            HeapFree(GetProcessHeap(), 0, dir);
+            /* sizeof(CURSORICONFILEDIRENTRY) for each entry, +6 for magic &
+             * count. */
+            dir = HeapAlloc(GetProcessHeap(), 0,
+                (count * sizeof(CURSORICONFILEDIRENTRY)) + 6);
+            max_count = count;
+        }
+
+        /* sizeof(CURSORICONFILEDIRENTRY) for each entry, +6 for magic &
+         * count. */
+        memcpy(dir, icon_data, (count * sizeof(CURSORICONFILEDIRENTRY)) + 6);
+        entry = CURSORICON_FindBestIconFile(dir, width, height, colors);
+
+        if (frame_bits_size < entry->dwDIBSize)
+        {
+            frame_bits_size = entry->dwDIBSize;
+            HeapFree(GetProcessHeap(), 0, frame_bits);
+            frame_bits = HeapAlloc( GetProcessHeap(), 0, frame_bits_size );
+        }
+
+        if (!header.width || !header.height)
+        {
+            header.width = entry->bWidth;
+            header.height = entry->bHeight;
+        }
+
+        hotspot.x = entry->xHotspot;
+        hotspot.y = entry->yHotspot;
+
+        memcpy(frame_bits, icon_data + entry->dwDIBOffset, entry->dwDIBSize);
 
-    entry = CURSORICON_FindBestIconFile( (CURSORICONFILEDIR *) icon_data,
-        width, height, colors );
+        frame_map[i].frame = CURSORICON_CreateFrameFromBMI(
+            (BITMAPINFO *) frame_bits, hotspot, FALSE, 0x00030000,
+            header.width, header.height, 0, &frame_map[i].size);
+        if (!frame_map[i].frame)
+        {
+            ERR("Unable to retrieve frame.\n");
+            goto cleanup;
+        }
 
-    frame_bits = HeapAlloc( GetProcessHeap(), 0, entry->dwDIBSize );
-    memcpy( frame_bits, icon_data + entry->dwDIBOffset, entry->dwDIBSize );
+        /* Advance to the next chunk. */
+        icon_chunk += chunk_size + (2 * sizeof(DWORD));
+        icon_data = icon_chunk + (2 * sizeof(DWORD));
+    }
 
-    if (!header.width || !header.height)
+    /* Calculate frames_size. */
+    for (i = 0; i < header.num_steps; i++)
     {
-        header.width = entry->bWidth;
-        header.height = entry->bHeight;
+        if (seq)
+            frames_size += frame_map[seq[i]].size;
+        else
+            frames_size += frame_map[i].size;
     }
 
-    hotspot.x = entry->xHotspot;
-    hotspot.y = entry->yHotspot;
+    cursor_data = HeapAlloc(GetProcessHeap(), 0,
+        sizeof(struct cursor) + frames_size);
+    if (cursor_data)
+    {
+        BYTE *next;
+
+        cursor_data->num_frames = header.num_steps;
+        cursor_data->delay = header.display_rate;
+
+        /* Loop through frames and copy. */
+        next = (BYTE *) (cursor_data + 1);
+        for (i = 0; i < header.num_steps; i++)
+        {
+            if (seq)
+            {
+                if (frame_map[seq[i]].frame)
+                {
+                    memcpy(next, frame_map[seq[i]].frame,
+                        frame_map[seq[i]].size);
+                }
+                next += frame_map[seq[i]].size;
+            }
+            else
+            {
+                if (frame_map[i].frame)
+                    memcpy(next, frame_map[i].frame, frame_map[i].size);
+                next += frame_map[i].size;
+            }
+        }
+
+        /* Make 32-bit cursor. */
+        cursor = create_cursor(0, cursor_data,
+            sizeof(struct cursor) + frames_size);
+    }
 
-    cursor = CURSORICON_CreateIconFromBMI( (BITMAPINFO *) frame_bits, hotspot,
-        FALSE, 0x00030000, header.width, header.height, 0 );
+cleanup:
+    for (i = 0; i < header.num_frames; i++)
+        HeapFree(GetProcessHeap(), 0, frame_map[i].frame);
 
-    HeapFree( GetProcessHeap(), 0, frame_bits );
+    HeapFree(GetProcessHeap(), 0, frame_bits);
+    HeapFree(GetProcessHeap(), 0, dir);
 
     return cursor;
 }
diff --git a/dlls/winex11.drv/mouse.c b/dlls/winex11.drv/mouse.c
index 29932f3..10f1d31 100644
--- a/dlls/winex11.drv/mouse.c
+++ b/dlls/winex11.drv/mouse.c
@@ -33,6 +33,9 @@ static void *xcursor_handle;
 MAKE_FUNCPTR(XcursorImageCreate);
 MAKE_FUNCPTR(XcursorImageDestroy);
 MAKE_FUNCPTR(XcursorImageLoadCursor);
+MAKE_FUNCPTR(XcursorImagesCreate);
+MAKE_FUNCPTR(XcursorImagesDestroy);
+MAKE_FUNCPTR(XcursorImagesLoadCursor);
 # undef MAKE_FUNCPTR
 #endif /* SONAME_LIBXCURSOR */
 
@@ -113,6 +116,9 @@ void X11DRV_Xcursor_Init(void)
     LOAD_FUNCPTR(XcursorImageCreate);
     LOAD_FUNCPTR(XcursorImageDestroy);
     LOAD_FUNCPTR(XcursorImageLoadCursor);
+    LOAD_FUNCPTR(XcursorImagesCreate);
+    LOAD_FUNCPTR(XcursorImagesDestroy);
+    LOAD_FUNCPTR(XcursorImagesLoadCursor);
 #undef LOAD_FUNCPTR
 #endif /* SONAME_LIBXCURSOR */
 }
@@ -448,7 +454,7 @@ static BOOL check_alpha_zero(CURSORICONINFO *ptr, unsigned char *xor_bits)
  *
  * Create an XcursorImage from a CURSORICONINFO
  */
-static XcursorImage *create_cursor_image( CURSORICONINFO *ptr )
+static XcursorImage *create_cursor_image(CURSORICONINFO *ptr, SIZE_T *offset)
 {
     static const unsigned char convert_5to8[] =
     {
@@ -486,6 +492,9 @@ static XcursorImage *create_cursor_image( CURSORICONINFO *ptr )
 
     xor_ptr = xor_bits = and_ptr + and_size;
 
+    *offset =
+        sizeof(CURSORICONINFO) + and_size + xor_width_bytes * ptr->nHeight;
+
     image = pXcursorImageCreate( ptr->nWidth, ptr->nHeight );
     pixel_ptr = image->pixels;
 
@@ -583,7 +592,10 @@ static Cursor create_xcursor_cursor(Display *display,
 {
     Cursor cursor;
     XcursorImage *image;
+    XcursorImages *images;
     CURSORICONINFO *ptr;
+    UINT i;
+    SIZE_T offset;
 
     if (!cursor_data || !cursor_data->num_frames) /* Create an empty cursor. */
     {
@@ -598,23 +610,34 @@ static Cursor create_xcursor_cursor(Display *display,
     }
 
     ptr = (CURSORICONINFO *) (cursor_data + 1);
-    image = create_cursor_image( ptr );
-    if (!image) return 0;
-
-    /* Make sure hotspot is valid */
-    image->xhot = ptr->ptHotSpot.x;
-    image->yhot = ptr->ptHotSpot.y;
-    if (image->xhot >= image->width ||
-        image->yhot >= image->height)
+
+    images = pXcursorImagesCreate(cursor_data->num_frames);
+    for (i = 0; i < cursor_data->num_frames; i++)
     {
-        image->xhot = image->width / 2;
-        image->yhot = image->height / 2;
-    }
+        image = create_cursor_image(ptr, &offset);
+        if (!image)
+            return 0;
+
+        /* Make sure hotspot is valid */
+        image->xhot = ptr->ptHotSpot.x;
+        image->yhot = ptr->ptHotSpot.y;
+        if (image->xhot >= image->width ||
+            image->yhot >= image->height)
+        {
+            image->xhot = image->width / 2;
+            image->yhot = image->height / 2;
+        }
 
-    image->delay = 0;
+        /* The .ANI stores the display rate in 1/60s, X11 stores the delay
+         * between frames in ms. */
+        image->delay = (100 * cursor_data->delay) / 6;
+
+        images->images[images->nimage++] = image;
+        ptr = (CURSORICONINFO *) (((BYTE *) ptr) + offset);
+    }
 
-    cursor = pXcursorImageLoadCursor( display, image );
-    pXcursorImageDestroy( image );
+    cursor = pXcursorImagesLoadCursor(display, images);
+    pXcursorImagesDestroy(images);
 
     return cursor;
 }
-- 
1.5.6.3

