Currently shell32 only transfers the plain icon for `Shell_NotifyIcon` calls, ignoring balloon icons. This patch allows transferring both images to explorer.exe tray.
-- v14: shell32: add support for balloon icon copying
From: Sergei Chernyadyev serg.cherniadjev@gmail.com
--- dlls/shell32/systray.c | 37 ++++++++++++++++++++++--------------- programs/explorer/systray.c | 29 ++++++++++++++++++----------- 2 files changed, 40 insertions(+), 26 deletions(-)
diff --git a/dlls/shell32/systray.c b/dlls/shell32/systray.c index 78717cfcdca..443bf470b2c 100644 --- a/dlls/shell32/systray.c +++ b/dlls/shell32/systray.c @@ -34,12 +34,22 @@
WINE_DEFAULT_DEBUG_CHANNEL(systray);
+struct notify_data_icon +{ + /* data for the icon bitmap */ + UINT width; + UINT height; + UINT planes; + UINT bpp; +}; + struct notify_data /* platform-independent format for NOTIFYICONDATA */ { LONG hWnd; UINT uID; UINT uFlags; UINT uCallbackMessage; + struct notify_data_icon icon_info; /* systray icon bitmap info */ WCHAR szTip[128]; DWORD dwState; DWORD dwStateMask; @@ -51,13 +61,10 @@ struct notify_data /* platform-independent format for NOTIFYICONDATA */ WCHAR szInfoTitle[64]; DWORD dwInfoFlags; GUID guidItem; - /* data for the icon bitmap */ - UINT width; - UINT height; - UINT planes; - UINT bpp; + BYTE icon_data[]; };
+ /************************************************************************* * Shell_NotifyIcon [SHELL32.296] * Shell_NotifyIconA [SHELL32.297] @@ -164,7 +171,7 @@ BOOL WINAPI Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW nid) BITMAP bmColour; LONG cbMaskBits; LONG cbColourBits = 0; - char *buffer; + BYTE *buffer;
if (!GetIconInfo(nid->hIcon, &iconinfo)) goto noicon; @@ -192,21 +199,21 @@ BOOL WINAPI Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW nid)
data = (struct notify_data *)buffer; memset( data, 0, sizeof(*data) ); - buffer += sizeof(*data); + buffer = data->icon_data; GetBitmapBits(iconinfo.hbmMask, cbMaskBits, buffer); if (!iconinfo.hbmColor) { - data->width = bmMask.bmWidth; - data->height = bmMask.bmHeight / 2; - data->planes = 1; - data->bpp = 1; + data->icon_info.width = bmMask.bmWidth; + data->icon_info.height = bmMask.bmHeight / 2; + data->icon_info.planes = 1; + data->icon_info.bpp = 1; } else { - data->width = bmColour.bmWidth; - data->height = bmColour.bmHeight; - data->planes = bmColour.bmPlanes; - data->bpp = bmColour.bmBitsPixel; + data->icon_info.width = bmColour.bmWidth; + data->icon_info.height = bmColour.bmHeight; + data->icon_info.planes = bmColour.bmPlanes; + data->icon_info.bpp = bmColour.bmBitsPixel; buffer += cbMaskBits; GetBitmapBits(iconinfo.hbmColor, cbColourBits, buffer); DeleteObject(iconinfo.hbmColor); diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index b47c2239abd..6337faad633 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -36,12 +36,22 @@ WINE_DEFAULT_DEBUG_CHANNEL(systray); #define TRAY_MINIMIZE_ALL 419 #define TRAY_MINIMIZE_ALL_UNDO 416
+struct notify_data_icon +{ + /* data for the icon bitmap */ + UINT width; + UINT height; + UINT planes; + UINT bpp; +}; + struct notify_data /* platform-independent format for NOTIFYICONDATA */ { LONG hWnd; UINT uID; UINT uFlags; UINT uCallbackMessage; + struct notify_data_icon icon_info; /* systray icon bitmap info */ WCHAR szTip[128]; DWORD dwState; DWORD dwStateMask; @@ -53,11 +63,7 @@ struct notify_data /* platform-independent format for NOTIFYICONDATA */ WCHAR szInfoTitle[64]; DWORD dwInfoFlags; GUID guidItem; - /* data for the icon bitmap */ - UINT width; - UINT height; - UINT planes; - UINT bpp; + BYTE icon_data[]; };
#define ICON_DISPLAY_HIDDEN -1 @@ -836,11 +842,13 @@ static BOOL handle_incoming(HWND hwndSource, COPYDATASTRUCT *cds) { struct icon *icon = NULL; const struct notify_data *data; + const BYTE *icon_data; NOTIFYICONDATAW nid; int ret = FALSE;
if (cds->cbData < sizeof(*data)) return FALSE; data = cds->lpData; + icon_data = data->icon_data;
nid.cbSize = sizeof(nid); nid.hWnd = LongToHandle( data->hWnd ); @@ -860,22 +868,21 @@ static BOOL handle_incoming(HWND hwndSource, COPYDATASTRUCT *cds)
/* FIXME: if statement only needed because we don't support interprocess * icon handles */ - if ((nid.uFlags & NIF_ICON) && cds->cbData > sizeof(*data)) + if (nid.uFlags & NIF_ICON) { LONG cbMaskBits; LONG cbColourBits; - const char *buffer = (const char *)(data + 1);
- cbMaskBits = (data->width * data->height + 15) / 16 * 2; - cbColourBits = (data->planes * data->width * data->height * data->bpp + 15) / 16 * 2; + cbMaskBits = (data->icon_info.width * data->icon_info.height + 15) / 16 * 2; + cbColourBits = (data->icon_info.planes * data->icon_info.width * data->icon_info.height * data->icon_info.bpp + 15) / 16 * 2;
if (cds->cbData < sizeof(*data) + cbMaskBits + cbColourBits) { ERR( "buffer underflow\n" ); return FALSE; } - nid.hIcon = CreateIcon(NULL, data->width, data->height, data->planes, data->bpp, - buffer, buffer + cbMaskBits); + nid.hIcon = CreateIcon(NULL, data->icon_info.width, data->icon_info.height, data->icon_info.planes, data->icon_info.bpp, + icon_data, icon_data + cbMaskBits); }
/* try forwarding to the display driver first */
From: Sergei Chernyadyev serg.cherniadjev@gmail.com
--- dlls/shell32/systray.c | 137 +++++++++++++++++++++++++----------- programs/explorer/systray.c | 32 ++++++++- 2 files changed, 125 insertions(+), 44 deletions(-)
diff --git a/dlls/shell32/systray.c b/dlls/shell32/systray.c index 443bf470b2c..afb4ac586a0 100644 --- a/dlls/shell32/systray.c +++ b/dlls/shell32/systray.c @@ -61,6 +61,7 @@ struct notify_data /* platform-independent format for NOTIFYICONDATA */ WCHAR szInfoTitle[64]; DWORD dwInfoFlags; GUID guidItem; + struct notify_data_icon balloon_icon_info; /* balloon icon bitmap info */ BYTE icon_data[]; };
@@ -123,6 +124,58 @@ BOOL WINAPI Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA pnid) return Shell_NotifyIconW(dwMessage, &nidW); }
+ +/************************************************************************* + * get_bitmap_info Helper function for filling BITMAP structs and calculating buffer size in bits + */ +static void get_bitmap_info( ICONINFO* iconinfo, BITMAP* bmMask, BITMAP* bmColour, LONG* cbMaskBits, LONG* cbColourBits ) +{ + if ((iconinfo->hbmMask && !GetObjectW(iconinfo->hbmMask, sizeof(*bmMask), bmMask)) || + (iconinfo->hbmColor && !GetObjectW(iconinfo->hbmColor, sizeof(*bmColour), bmColour))) + { + DeleteObject(iconinfo->hbmMask); + if (iconinfo->hbmColor) DeleteObject(iconinfo->hbmColor); + memset(iconinfo, 0, sizeof(*iconinfo)); + return; + } + + if (iconinfo->hbmMask) + *cbMaskBits = (bmMask->bmPlanes * bmMask->bmWidth * bmMask->bmHeight * bmMask->bmBitsPixel + 15) / 16 * 2; + if (iconinfo->hbmColor) + *cbColourBits = (bmColour->bmPlanes * bmColour->bmWidth * bmColour->bmHeight * bmColour->bmBitsPixel + 15) / 16 * 2; +} + + +/************************************************************************* + * fill_icon_info Helper function for filling struct image metadata and buffer with bitmap data + */ +static void fill_icon_info( const BITMAP* bmMask, const BITMAP* bmColour, LONG cbMaskBits, LONG cbColourBits, + ICONINFO* iconinfo, struct notify_data_icon* msg_icon_info, BYTE* image_data_buffer ) +{ + if (iconinfo->hbmColor) + { + msg_icon_info->width = bmColour->bmWidth; + msg_icon_info->height = bmColour->bmHeight; + msg_icon_info->planes = bmColour->bmPlanes; + msg_icon_info->bpp = bmColour->bmBitsPixel; + } + else + { + msg_icon_info->width = bmMask->bmWidth; + msg_icon_info->height = bmMask->bmHeight / 2; + msg_icon_info->planes = 1; + msg_icon_info->bpp = 1; + } + + GetBitmapBits(iconinfo->hbmMask, cbMaskBits, image_data_buffer); + if (iconinfo->hbmColor) + { + GetBitmapBits(iconinfo->hbmColor, cbColourBits, image_data_buffer + cbMaskBits); + DeleteObject(iconinfo->hbmColor); + } + DeleteObject(iconinfo->hbmMask); +} + /************************************************************************* * Shell_NotifyIconW [SHELL32.298] */ @@ -132,6 +185,11 @@ BOOL WINAPI Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW nid) COPYDATASTRUCT cds; struct notify_data data_buffer; struct notify_data *data = &data_buffer; + ICONINFO iconinfo = { 0 }, ballooniconinfo = { 0 }; + BITMAP bmMask, bmBalloonMask; + BITMAP bmColour, bmBalloonColour; + LONG cbMaskBits = 0, balloonCbMaskBits = 0; + LONG cbColourBits = 0, balloonCbColourBits = 0; BOOL ret;
TRACE("dwMessage = %ld, nid->cbSize=%ld\n", dwMessage, nid->cbSize); @@ -165,63 +223,56 @@ BOOL WINAPI Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW nid) /* FIXME: if statement only needed because we don't support interprocess * icon handles */ if (nid->uFlags & NIF_ICON) - { - ICONINFO iconinfo; - BITMAP bmMask; - BITMAP bmColour; - LONG cbMaskBits; - LONG cbColourBits = 0; - BYTE *buffer; - - if (!GetIconInfo(nid->hIcon, &iconinfo)) - goto noicon; - - if (!GetObjectW(iconinfo.hbmMask, sizeof(bmMask), &bmMask) || - (iconinfo.hbmColor && !GetObjectW(iconinfo.hbmColor, sizeof(bmColour), &bmColour))) - { - DeleteObject(iconinfo.hbmMask); - if (iconinfo.hbmColor) DeleteObject(iconinfo.hbmColor); - goto noicon; - } + GetIconInfo(nid->hIcon, &iconinfo); + + if ((nid->uFlags & NIF_INFO) && (nid->dwInfoFlags & NIIF_USER)) + GetIconInfo(nid->hBalloonIcon, &ballooniconinfo); + + get_bitmap_info( &iconinfo, &bmMask, &bmColour, &cbMaskBits, &cbColourBits ); + cds.cbData += cbMaskBits + cbColourBits; + + get_bitmap_info( &ballooniconinfo, &bmBalloonMask, &bmBalloonColour, &balloonCbMaskBits, &balloonCbColourBits ); + cds.cbData += balloonCbMaskBits + balloonCbColourBits;
- cbMaskBits = (bmMask.bmPlanes * bmMask.bmWidth * bmMask.bmHeight * bmMask.bmBitsPixel + 15) / 16 * 2; - if (iconinfo.hbmColor) - cbColourBits = (bmColour.bmPlanes * bmColour.bmWidth * bmColour.bmHeight * bmColour.bmBitsPixel + 15) / 16 * 2; - cds.cbData = sizeof(*data) + cbMaskBits + cbColourBits; + if (cds.cbData > sizeof(*data)) + { + char *buffer; + BYTE* image_data_buffer; buffer = malloc(cds.cbData); if (!buffer) { - DeleteObject(iconinfo.hbmMask); - if (iconinfo.hbmColor) DeleteObject(iconinfo.hbmColor); + if (iconinfo.hbmMask) + DeleteObject(iconinfo.hbmMask); + + if (iconinfo.hbmColor) + DeleteObject(iconinfo.hbmColor); + + if (ballooniconinfo.hbmMask) + DeleteObject(ballooniconinfo.hbmMask); + + if (ballooniconinfo.hbmColor) + DeleteObject(ballooniconinfo.hbmColor); + SetLastError(E_OUTOFMEMORY); return FALSE; }
data = (struct notify_data *)buffer; + image_data_buffer = &data->icon_data[0]; memset( data, 0, sizeof(*data) ); - buffer = data->icon_data; - GetBitmapBits(iconinfo.hbmMask, cbMaskBits, buffer); - if (!iconinfo.hbmColor) - { - data->icon_info.width = bmMask.bmWidth; - data->icon_info.height = bmMask.bmHeight / 2; - data->icon_info.planes = 1; - data->icon_info.bpp = 1; - } - else + + if (iconinfo.hbmMask) { - data->icon_info.width = bmColour.bmWidth; - data->icon_info.height = bmColour.bmHeight; - data->icon_info.planes = bmColour.bmPlanes; - data->icon_info.bpp = bmColour.bmBitsPixel; - buffer += cbMaskBits; - GetBitmapBits(iconinfo.hbmColor, cbColourBits, buffer); - DeleteObject(iconinfo.hbmColor); + fill_icon_info( &bmMask, &bmColour, cbMaskBits, cbColourBits, + &iconinfo, &data->icon_info, image_data_buffer ); + image_data_buffer += cbMaskBits + cbColourBits; } - DeleteObject(iconinfo.hbmMask); + + if (ballooniconinfo.hbmMask) + fill_icon_info( &bmBalloonMask, &bmBalloonColour, balloonCbMaskBits, balloonCbColourBits, + &ballooniconinfo, &data->balloon_icon_info, image_data_buffer ); }
-noicon: data->hWnd = HandleToLong( nid->hWnd ); data->uID = nid->uID; data->uFlags = nid->uFlags; diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 6337faad633..913f365d289 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -63,6 +63,7 @@ struct notify_data /* platform-independent format for NOTIFYICONDATA */ WCHAR szInfoTitle[64]; DWORD dwInfoFlags; GUID guidItem; + struct notify_data_icon balloon_icon_info; /* balloon icon bitmap info */ BYTE icon_data[]; };
@@ -729,7 +730,17 @@ static BOOL modify_icon( struct icon *icon, NOTIFYICONDATAW *nid ) lstrcpynW( icon->info_title, nid->szInfoTitle, ARRAY_SIZE( icon->info_title )); icon->info_flags = nid->dwInfoFlags; icon->info_timeout = max(min(nid->uTimeout, BALLOON_SHOW_MAX_TIMEOUT), BALLOON_SHOW_MIN_TIMEOUT); - icon->info_icon = nid->hBalloonIcon; + + if (icon->info_icon) DestroyIcon(icon->info_icon); + if ((icon->info_flags & NIIF_ICONMASK) == NIIF_USER) + { + icon->info_icon = CopyIcon(nid->hBalloonIcon); + } + else + { + icon->info_icon = NULL; + } + update_balloon( icon ); } if (icon->state & NIS_HIDDEN) hide_icon( icon ); @@ -883,8 +894,26 @@ static BOOL handle_incoming(HWND hwndSource, COPYDATASTRUCT *cds) } nid.hIcon = CreateIcon(NULL, data->icon_info.width, data->icon_info.height, data->icon_info.planes, data->icon_info.bpp, icon_data, icon_data + cbMaskBits); + icon_data += cbMaskBits + cbColourBits; }
+ if ((nid.uFlags & NIF_INFO) && (nid.dwInfoFlags & NIIF_USER)) + { + /* Balloon icon */ + LONG cbMaskBits; + LONG cbColourBits; + + cbMaskBits = (data->balloon_icon_info.width * data->balloon_icon_info.height + 15) / 16 * 2; + cbColourBits = (data->balloon_icon_info.planes * data->balloon_icon_info.width * data->balloon_icon_info.height * data->balloon_icon_info.bpp + 15) / 16 * 2; + + if (cds->cbData < ((char*)icon_data - (char*)data) + cbMaskBits + cbColourBits) + { + ERR( "buffer underflow\n" ); + return FALSE; + } + nid.hBalloonIcon = CreateIcon(NULL, data->balloon_icon_info.width, data->balloon_icon_info.height, data->balloon_icon_info.planes, data->balloon_icon_info.bpp, + icon_data, icon_data + cbMaskBits); + } /* try forwarding to the display driver first */ if (cds->dwData == NIM_ADD || !(icon = get_icon( nid.hWnd, nid.uID ))) { @@ -919,6 +948,7 @@ static BOOL handle_incoming(HWND hwndSource, COPYDATASTRUCT *cds)
done: if (nid.hIcon) DestroyIcon( nid.hIcon ); + if (nid.hBalloonIcon) DestroyIcon( nid.hBalloonIcon ); sync_taskbar_buttons(); return ret; }
Rémi Bernon (@rbernon) commented about dlls/shell32/systray.c:
/* FIXME: if statement only needed because we don't support interprocess * icon handles */ if (nid->uFlags & NIF_ICON)
- {
ICONINFO iconinfo;
BITMAP bmMask;
BITMAP bmColour;
LONG cbMaskBits;
LONG cbColourBits = 0;
char *buffer;
GetIconInfo(nid->hIcon, &iconinfo);
if (!GetIconInfo(nid->hIcon, &iconinfo))
goto noicon;
- if ((nid->uFlags & NIF_INFO) && (nid->dwInfoFlags & NIIF_USER))
Shouldn't this be `(nid->dwInfoFlags & NIIF_ICONMASK) == NIIF_USER` instead to match the condition in explorer? I'm just guessing, I don't know what these flags exactly are.
Almost good imo, I've made some adjustments (split, various nits cleanup) in https://gitlab.winehq.org/rbernon/wine/-/commits/tmp-2875, if you don't mind having a look and updating the MR with these changes.
I used `(nid->dwInfoFlags & NIIF_ICONMASK) == NIIF_USER` instead, but like I said above I'm not sure about it so feel free to change it back.
On Wed Sep 18 06:51:24 2024 +0000, Rémi Bernon wrote:
Shouldn't this be `(nid->dwInfoFlags & NIIF_ICONMASK) == NIIF_USER` instead to match the condition in explorer? I'm just guessing, I don't know what these flags exactly are.
yup, it should look exactly like this, my implementation doesn't really matter because NIIF_USER work basically like a flag on its own