Adds the tray icons implementation based on org.kde.StatusNotifierItem interface usage. Does allow restarting StatusNotifierWatcher object, but will fallback to XEMBED or internal tray, if wine gets initialized when there is no StatusNotifierWatcher object registered.
-- v28: win32u: add SNI driver for systray handling win32u: add a SystrayRunLoop driver interface win32u: Refactor NotifyIcon driver interface into separate calls.
From: Sergei Chernyadyev 1892-Cherser-s@users.noreply.gitlab.winehq.org
--- dlls/win32u/driver.c | 12 ++++++++++++ dlls/win32u/systray.c | 3 +++ dlls/wow64win/user.c | 19 +++++++++++++++++++ include/ntuser.h | 10 ++++++++++ include/wine/gdi_driver.h | 1 + programs/explorer/systray.c | 13 ++++++++++++- 6 files changed, 57 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/driver.c b/dlls/win32u/driver.c index bc3409a9e34..3b6187d678e 100644 --- a/dlls/win32u/driver.c +++ b/dlls/win32u/driver.c @@ -793,6 +793,11 @@ static BOOL nulldrv_SystrayDockRemove( HWND hwnd ) return FALSE; }
+static BOOL nulldrv_SystrayShowBalloon( HWND hwnd, UINT uID, BOOL hidden, struct systray_balloon *icon ) +{ + return FALSE; +} + static void nulldrv_UpdateClipboard(void) { } @@ -1214,6 +1219,11 @@ static BOOL loaderdrv_SystrayDockRemove( HWND hwnd ) return load_driver()->pSystrayDockRemove( hwnd ); }
+static BOOL loaderdrv_SystrayShowBalloon( HWND hwnd, UINT uID, BOOL hidden, struct systray_balloon *icon ) +{ + return load_driver()->pSystrayShowBalloon( hwnd, uID, hidden, icon ); +} + static LRESULT nulldrv_ClipboardWindowProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { return 0; @@ -1307,6 +1317,7 @@ static const struct user_driver_funcs lazy_load_driver = loaderdrv_SystrayDockInsert, loaderdrv_SystrayDockClear, loaderdrv_SystrayDockRemove, + loaderdrv_SystrayShowBalloon, /* clipboard functions */ nulldrv_ClipboardWindowProc, loaderdrv_UpdateClipboard, @@ -1397,6 +1408,7 @@ void __wine_set_user_driver( const struct user_driver_funcs *funcs, UINT version SET_USER_FUNC(SystrayDockInsert); SET_USER_FUNC(SystrayDockClear); SET_USER_FUNC(SystrayDockRemove); + SET_USER_FUNC(SystrayShowBalloon); SET_USER_FUNC(ClipboardWindowProc); SET_USER_FUNC(UpdateClipboard); SET_USER_FUNC(ChangeDisplaySettings); diff --git a/dlls/win32u/systray.c b/dlls/win32u/systray.c index 67217dad634..73e344e90e7 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -52,6 +52,9 @@ LRESULT system_tray_call( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, voi case WINE_SYSTRAY_DOCK_REMOVE: return user_driver->pSystrayDockRemove( hwnd );
+ case WINE_SYSTRAY_SHOW_BALLOON: + return user_driver->pSystrayShowBalloon( hwnd, wparam, lparam, data ); + default: FIXME( "Unknown NtUserSystemTrayCall msg %#x\n", msg ); break; diff --git a/dlls/wow64win/user.c b/dlls/wow64win/user.c index 44b422ef62c..ff78e74cc7a 100644 --- a/dlls/wow64win/user.c +++ b/dlls/wow64win/user.c @@ -3605,6 +3605,25 @@ NTSTATUS WINAPI wow64_NtUserMessageCall( UINT *args ) case NtUserSystemTrayCall: switch (msg) { + case WINE_SYSTRAY_SHOW_BALLOON: + { + struct + { + WCHAR info_text[256]; /* info balloon text */ + WCHAR info_title[64]; /* info balloon title */ + UINT info_flags; /* flags for info balloon */ + UINT info_timeout; /* timeout for info balloon */ + ULONG info_icon; /* info balloon icon */ + } *balloon_params32 = result_info; + struct systray_balloon balloon_params; + balloon_params.info_flags = balloon_params32->info_flags; + balloon_params.info_timeout = balloon_params32->info_timeout; + balloon_params.info_icon = UlongToHandle(balloon_params32->info_icon); + wcscpy( balloon_params.info_text, balloon_params32->info_text ); + wcscpy( balloon_params.info_title, balloon_params32->info_title ); + + return NtUserMessageCall( hwnd, msg, wparam, lparam, &balloon_params, type, ansi ); + } case WINE_SYSTRAY_NOTIFY_ICON: { struct diff --git a/include/ntuser.h b/include/ntuser.h index dd19c57221e..eb416df71cb 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -530,6 +530,16 @@ enum wine_systray_call WINE_SYSTRAY_DOCK_INSERT, WINE_SYSTRAY_DOCK_CLEAR, WINE_SYSTRAY_DOCK_REMOVE, + WINE_SYSTRAY_SHOW_BALLOON, +}; + +struct systray_balloon +{ + WCHAR info_text[256]; /* info balloon text */ + WCHAR info_title[64]; /* info balloon title */ + UINT info_flags; /* flags for info balloon */ + UINT info_timeout; /* timeout for info balloon */ + HICON info_icon; /* info balloon icon */ };
#define WM_SYSTIMER 0x0118 diff --git a/include/wine/gdi_driver.h b/include/wine/gdi_driver.h index aaa2f92cbfa..9e9b2886bfa 100644 --- a/include/wine/gdi_driver.h +++ b/include/wine/gdi_driver.h @@ -313,6 +313,7 @@ struct user_driver_funcs BOOL (*pSystrayDockInsert)(HWND,UINT,UINT,void *); void (*pSystrayDockClear)(HWND); BOOL (*pSystrayDockRemove)(HWND); + BOOL (*pSystrayShowBalloon)(HWND,UINT,BOOL,struct systray_balloon *); /* clipboard functions */ LRESULT (*pClipboardWindowProc)(HWND,UINT,WPARAM,LPARAM); void (*pUpdateClipboard)(void); diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 1af8a72de46..75a857ac583 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -279,7 +279,18 @@ static void show_next_balloon(void)
static void update_balloon( struct icon *icon ) { - if (balloon_icon == icon) + struct systray_balloon balloon_info; + balloon_info.info_flags = icon->info_flags; + balloon_info.info_timeout = icon->info_timeout; + balloon_info.info_icon = icon->info_icon; + wcscpy( balloon_info.info_text, icon->info_text ); + wcscpy( balloon_info.info_title, icon->info_title ); + if (NtUserMessageCall( icon->window, WINE_SYSTRAY_SHOW_BALLOON, icon->id, icon->display == ICON_DISPLAY_HIDDEN, + &balloon_info, NtUserSystemTrayCall, FALSE ) > 0) + { + return; + } + else if (balloon_icon == icon) { hide_balloon( icon ); show_balloon( icon );
From: Sergei Chernyadyev 1892-Cherser-s@users.noreply.gitlab.winehq.org
--- dlls/win32u/driver.c | 46 ++++++++++++++++++++++++++++++++---- dlls/win32u/systray.c | 10 ++++++-- dlls/winemac.drv/gdi.c | 5 +++- dlls/winemac.drv/macdrv.h | 5 +++- dlls/winemac.drv/systray.c | 47 ++++++++++++++----------------------- dlls/wow64win/user.c | 3 ++- include/ntuser.h | 5 +++- include/wine/gdi_driver.h | 5 +++- programs/explorer/systray.c | 33 ++++++++++++++++++++++++-- 9 files changed, 116 insertions(+), 43 deletions(-)
diff --git a/dlls/win32u/driver.c b/dlls/win32u/driver.c index 3b6187d678e..ab7faf436c3 100644 --- a/dlls/win32u/driver.c +++ b/dlls/win32u/driver.c @@ -766,7 +766,22 @@ static BOOL nulldrv_ClipCursor( const RECT *clip, BOOL reset ) return TRUE; }
-static LRESULT nulldrv_NotifyIcon( HWND hwnd, UINT msg, NOTIFYICONDATAW *data ) +static LRESULT nulldrv_AddNotifyIcon( HWND hwnd, NOTIFYICONDATAW *data ) +{ + return -1; +} + +static LRESULT nulldrv_ModifyNotifyIcon( HWND hwnd, NOTIFYICONDATAW *data ) +{ + return -1; +} + +static LRESULT nulldrv_DeleteNotifyIcon( HWND hwnd, UINT uID ) +{ + return -1; +} + +static LRESULT nulldrv_SetNotifyIconVersion( HWND hwnd, UINT uID, UINT uVersion ) { return -1; } @@ -1189,9 +1204,24 @@ static BOOL loaderdrv_ClipCursor( const RECT *clip, BOOL reset ) return load_driver()->pClipCursor( clip, reset ); }
-static LRESULT loaderdrv_NotifyIcon( HWND hwnd, UINT msg, NOTIFYICONDATAW *data ) +static LRESULT loaderdrv_AddNotifyIcon( HWND hwnd, NOTIFYICONDATAW *data ) +{ + return load_driver()->pAddNotifyIcon( hwnd, data ); +} + +static LRESULT loaderdrv_ModifyNotifyIcon( HWND hwnd, NOTIFYICONDATAW *data ) +{ + return load_driver()->pModifyNotifyIcon( hwnd, data ); +} + +static LRESULT loaderdrv_DeleteNotifyIcon( HWND hwnd, UINT uID ) +{ + return load_driver()->pDeleteNotifyIcon( hwnd, uID ); +} + +static LRESULT loaderdrv_SetNotifyIconVersion( HWND hwnd, UINT uID, UINT uVersion ) { - return load_driver()->pNotifyIcon( hwnd, msg, data ); + return load_driver()->pSetNotifyIconVersion( hwnd, uID, uVersion ); }
static void loaderdrv_CleanupIcons( HWND hwnd ) @@ -1311,7 +1341,10 @@ static const struct user_driver_funcs lazy_load_driver = loaderdrv_SetCursorPos, loaderdrv_ClipCursor, /* systray functions */ - loaderdrv_NotifyIcon, + loaderdrv_AddNotifyIcon, + loaderdrv_ModifyNotifyIcon, + loaderdrv_DeleteNotifyIcon, + loaderdrv_SetNotifyIconVersion, loaderdrv_CleanupIcons, loaderdrv_SystrayDockInit, loaderdrv_SystrayDockInsert, @@ -1402,7 +1435,10 @@ void __wine_set_user_driver( const struct user_driver_funcs *funcs, UINT version SET_USER_FUNC(GetCursorPos); SET_USER_FUNC(SetCursorPos); SET_USER_FUNC(ClipCursor); - SET_USER_FUNC(NotifyIcon); + SET_USER_FUNC(AddNotifyIcon); + SET_USER_FUNC(ModifyNotifyIcon); + SET_USER_FUNC(DeleteNotifyIcon); + SET_USER_FUNC(SetNotifyIconVersion); SET_USER_FUNC(CleanupIcons); SET_USER_FUNC(SystrayDockInit); SET_USER_FUNC(SystrayDockInsert); diff --git a/dlls/win32u/systray.c b/dlls/win32u/systray.c index 73e344e90e7..3a62fa7c986 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -35,8 +35,14 @@ LRESULT system_tray_call( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, voi { switch (msg) { - case WINE_SYSTRAY_NOTIFY_ICON: - return user_driver->pNotifyIcon( hwnd, wparam, data ); + case WINE_SYSTRAY_ADD_NOTIFY_ICON: + return user_driver->pAddNotifyIcon( hwnd, data ); + case WINE_SYSTRAY_MODIFY_NOTIFY_ICON: + return user_driver->pModifyNotifyIcon( hwnd, data ); + case WINE_SYSTRAY_DELETE_NOTIFY_ICON: + return user_driver->pDeleteNotifyIcon( hwnd, wparam ); + case WINE_SYSTRAY_SET_NOTIFY_ICON_VERSION: + return user_driver->pSetNotifyIconVersion( hwnd, wparam, lparam ); case WINE_SYSTRAY_CLEANUP_ICONS: user_driver->pCleanupIcons( hwnd ); return 0; diff --git a/dlls/winemac.drv/gdi.c b/dlls/winemac.drv/gdi.c index 6c2241514a9..648e0f7e2f7 100644 --- a/dlls/winemac.drv/gdi.c +++ b/dlls/winemac.drv/gdi.c @@ -269,7 +269,10 @@ static const struct user_driver_funcs macdrv_funcs = .pBeep = macdrv_Beep, .pChangeDisplaySettings = macdrv_ChangeDisplaySettings, .pClipCursor = macdrv_ClipCursor, - .pNotifyIcon = macdrv_NotifyIcon, + .pAddNotifyIcon = macdrv_AddNotifyIcon, + .pModifyNotifyIcon = macdrv_ModifyNotifyIcon, + .pDeleteNotifyIcon = macdrv_DeleteNotifyIcon, + .pSetNotifyIconVersion = macdrv_SetNotifyIconVersion, .pCleanupIcons = macdrv_CleanupIcons, .pClipboardWindowProc = macdrv_ClipboardWindowProc, .pDesktopWindowProc = macdrv_DesktopWindowProc, diff --git a/dlls/winemac.drv/macdrv.h b/dlls/winemac.drv/macdrv.h index e1dd1c61b3f..6574091950f 100644 --- a/dlls/winemac.drv/macdrv.h +++ b/dlls/winemac.drv/macdrv.h @@ -134,7 +134,10 @@ extern BOOL macdrv_UpdateDisplayDevices( const struct gdi_device_manager *device extern BOOL macdrv_GetDeviceGammaRamp(PHYSDEV dev, LPVOID ramp); extern BOOL macdrv_SetDeviceGammaRamp(PHYSDEV dev, LPVOID ramp); extern BOOL macdrv_ClipCursor(const RECT *clip, BOOL reset); -extern LRESULT macdrv_NotifyIcon(HWND hwnd, UINT msg, NOTIFYICONDATAW *data); +extern LRESULT macdrv_AddNotifyIcon(HWND hwnd, NOTIFYICONDATAW *data); +extern LRESULT macdrv_ModifyNotifyIcon(HWND hwnd, NOTIFYICONDATAW *data); +extern LRESULT macdrv_DeleteNotifyIcon(HWND hwnd, UINT uID); +extern LRESULT macdrv_SetNotifyIconVersion(HWND hwnd, UINT uID, UINT uVersion); extern void macdrv_CleanupIcons(HWND hwnd); extern LRESULT macdrv_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); extern void macdrv_DestroyWindow(HWND hwnd); diff --git a/dlls/winemac.drv/systray.c b/dlls/winemac.drv/systray.c index 0231e08e82b..f339471a235 100644 --- a/dlls/winemac.drv/systray.c +++ b/dlls/winemac.drv/systray.c @@ -88,16 +88,19 @@ static struct tray_icon *get_icon(HWND owner, UINT id)
/*********************************************************************** - * modify_icon + * macdrv_ModifyNotifyIcon * * Modifies an existing tray icon and updates its status item as needed. */ -static BOOL modify_icon(struct tray_icon *icon, NOTIFYICONDATAW *nid) +static BOOL macdrv_ModifyNotifyIcon(HWND hwnd, NOTIFYICONDATAW *nid) { BOOL update_image = FALSE, update_tooltip = FALSE; + struct tray_icon *icon = get_icon(nid->hWnd, nid->uID);
TRACE("hwnd %p id 0x%x flags %x\n", nid->hWnd, nid->uID, nid->uFlags);
+ if (!icon) return FALSE; + if (nid->uFlags & NIF_STATE) { DWORD changed = (icon->state ^ nid->dwState) & nid->dwStateMask; @@ -185,7 +188,7 @@ static BOOL modify_icon(struct tray_icon *icon, NOTIFYICONDATAW *nid) * * Creates a new tray icon structure and adds it to the list. */ -static BOOL add_icon(NOTIFYICONDATAW *nid) +LRESULT macdrv_AddNotifyIcon(HWND hwnd, NOTIFYICONDATAW *nid) { NOTIFYICONDATAW new_nid; struct tray_icon *icon; @@ -242,38 +245,24 @@ static BOOL delete_icon(struct tray_icon *icon) return TRUE; }
+LRESULT macdrv_DeleteNotifyIcon(HWND hwnd, UINT uID) +{ + struct tray_icon *icon = get_icon(nid->hWnd, nid->uID); + if (!icon) return FALSE;
+ return delete_icon(icon); +} /*********************************************************************** * NotifyIcon (MACDRV.@) */ -LRESULT macdrv_NotifyIcon(HWND hwnd, UINT msg, NOTIFYICONDATAW *data) +LRESULT macdrv_SetNotifyIconVersion(HWND hwnd, UINT uID, UINT uVersion) { - BOOL ret = FALSE; - struct tray_icon *icon; + struct tray_icon *icon = get_icon(hwnd, uID);
- switch (msg) - { - case NIM_ADD: - ret = add_icon(data); - break; - case NIM_DELETE: - if ((icon = get_icon(data->hWnd, data->uID))) ret = delete_icon(icon); - break; - case NIM_MODIFY: - if ((icon = get_icon(data->hWnd, data->uID))) ret = modify_icon(icon, data); - break; - case NIM_SETVERSION: - if ((icon = get_icon(data->hWnd, data->uID))) - { - icon->version = data->uVersion; - ret = TRUE; - } - break; - default: - ERR("Unexpected NotifyIconProc call\n"); - return -1; - } - return ret; + if (!icon) return FALSE; + + icon->version = data->uVersion; + return TRUE; }
static BOOL notify_owner(struct tray_icon *icon, UINT msg, int x, int y) diff --git a/dlls/wow64win/user.c b/dlls/wow64win/user.c index ff78e74cc7a..aee16e5261f 100644 --- a/dlls/wow64win/user.c +++ b/dlls/wow64win/user.c @@ -3624,7 +3624,8 @@ NTSTATUS WINAPI wow64_NtUserMessageCall( UINT *args )
return NtUserMessageCall( hwnd, msg, wparam, lparam, &balloon_params, type, ansi ); } - case WINE_SYSTRAY_NOTIFY_ICON: + case WINE_SYSTRAY_ADD_NOTIFY_ICON: + case WINE_SYSTRAY_MODIFY_NOTIFY_ICON: { struct { diff --git a/include/ntuser.h b/include/ntuser.h index eb416df71cb..d188c7bb92c 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -524,7 +524,10 @@ struct ime_driver_call_params /* NtUserSystemTrayCall calls */ enum wine_systray_call { - WINE_SYSTRAY_NOTIFY_ICON, + WINE_SYSTRAY_ADD_NOTIFY_ICON, + WINE_SYSTRAY_MODIFY_NOTIFY_ICON, + WINE_SYSTRAY_DELETE_NOTIFY_ICON, + WINE_SYSTRAY_SET_NOTIFY_ICON_VERSION, WINE_SYSTRAY_CLEANUP_ICONS, WINE_SYSTRAY_DOCK_INIT, WINE_SYSTRAY_DOCK_INSERT, diff --git a/include/wine/gdi_driver.h b/include/wine/gdi_driver.h index 9e9b2886bfa..608b5494b0f 100644 --- a/include/wine/gdi_driver.h +++ b/include/wine/gdi_driver.h @@ -307,7 +307,10 @@ struct user_driver_funcs BOOL (*pSetCursorPos)(INT,INT); BOOL (*pClipCursor)(const RECT*,BOOL); /* notify icon functions */ - LRESULT (*pNotifyIcon)(HWND,UINT,NOTIFYICONDATAW *); + LRESULT (*pAddNotifyIcon)(HWND,NOTIFYICONDATAW *); + LRESULT (*pModifyNotifyIcon)(HWND,NOTIFYICONDATAW *); + LRESULT (*pDeleteNotifyIcon)(HWND,UINT); + LRESULT (*pSetNotifyIconVersion)(HWND,UINT,UINT); void (*pCleanupIcons)(HWND); void (*pSystrayDockInit)(HWND); BOOL (*pSystrayDockInsert)(HWND,UINT,UINT,void *); diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 75a857ac583..9da95af6bac 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -847,8 +847,37 @@ static BOOL handle_incoming(HWND hwndSource, COPYDATASTRUCT *cds) /* try forwarding to the display driver first */ if (cds->dwData == NIM_ADD || !(icon = get_icon( nid.hWnd, nid.uID ))) { - if ((ret = NtUserMessageCall( hwndSource, WINE_SYSTRAY_NOTIFY_ICON, cds->dwData, 0, - &nid, NtUserSystemTrayCall, FALSE )) != -1) + switch (cds->dwData) + { + case NIM_ADD: + { + ret = NtUserMessageCall( hwndSource, WINE_SYSTRAY_ADD_NOTIFY_ICON, 0, 0, + &nid, NtUserSystemTrayCall, FALSE ); + break; + } + case NIM_MODIFY: + { + ret = NtUserMessageCall( hwndSource, WINE_SYSTRAY_MODIFY_NOTIFY_ICON, 0, 0, + &nid, NtUserSystemTrayCall, FALSE ); + break; + } + case NIM_DELETE: + { + ret = NtUserMessageCall( hwndSource, WINE_SYSTRAY_DELETE_NOTIFY_ICON, nid.uID, 0, + NULL, NtUserSystemTrayCall, FALSE ); + break; + } + case NIM_SETVERSION: + { + ret = NtUserMessageCall( hwndSource, WINE_SYSTRAY_SET_NOTIFY_ICON_VERSION, nid.uID, + nid.uVersion, NULL, NtUserSystemTrayCall, FALSE ); + break; + } + default: + ret = -1; + break; + } + if (ret != -1) goto done; ret = FALSE; }
From: Sergei Chernyadyev 1892-Cherser-s@users.noreply.gitlab.winehq.org
--- dlls/win32u/systray.c | 2 ++ include/ntuser.h | 1 + programs/explorer/systray.c | 7 +++++++ 3 files changed, 10 insertions(+)
diff --git a/dlls/win32u/systray.c b/dlls/win32u/systray.c index 3a62fa7c986..1df2b97da9a 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -61,6 +61,8 @@ LRESULT system_tray_call( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, voi case WINE_SYSTRAY_SHOW_BALLOON: return user_driver->pSystrayShowBalloon( hwnd, wparam, lparam, data );
+ case WINE_SYSTRAY_RUN_LOOP: + return -1; default: FIXME( "Unknown NtUserSystemTrayCall msg %#x\n", msg ); break; diff --git a/include/ntuser.h b/include/ntuser.h index d188c7bb92c..9fabefeae62 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -534,6 +534,7 @@ enum wine_systray_call WINE_SYSTRAY_DOCK_CLEAR, WINE_SYSTRAY_DOCK_REMOVE, WINE_SYSTRAY_SHOW_BALLOON, + WINE_SYSTRAY_RUN_LOOP, };
struct systray_balloon diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 9da95af6bac..dae0089aec6 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -1144,6 +1144,11 @@ void handle_parent_notify( HWND hwnd, WPARAM wp ) sync_taskbar_buttons(); }
+static DWORD WINAPI systray_run_loop(void* arg) +{ + return NtUserMessageCall( tray_window, WINE_SYSTRAY_RUN_LOOP, 0, 0, NULL, NtUserSystemTrayCall, FALSE ) == 0; +} + /* this function creates the listener window */ void initialize_systray( BOOL using_root, BOOL arg_enable_shell ) { @@ -1187,6 +1192,8 @@ void initialize_systray( BOOL using_root, BOOL arg_enable_shell ) tray_window = CreateWindowExW( 0, shell_traywnd_class.lpszClassName, L"", WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, CW_USEDEFAULT, size.cx, size.cy, 0, 0, 0, 0 ); NtUserMessageCall( tray_window, WINE_SYSTRAY_DOCK_INIT, 0, 0, NULL, NtUserSystemTrayCall, FALSE ); + /* run loop if SNI is being used */ + CloseHandle(CreateThread(NULL, 0, systray_run_loop, NULL, 0, NULL)); }
if (!tray_window)
From: Sergei Chernyadyev 1892-Cherser-s@users.noreply.gitlab.winehq.org
--- dlls/win32u/Makefile.in | 6 +- dlls/win32u/snidrv/dbus.c | 1731 +++++++++++++++++++++++++++++++++++ dlls/win32u/snidrv/image.c | 167 ++++ dlls/win32u/snidrv/snidrv.h | 45 + dlls/win32u/systray.c | 49 +- 5 files changed, 1995 insertions(+), 3 deletions(-) create mode 100644 dlls/win32u/snidrv/dbus.c create mode 100644 dlls/win32u/snidrv/image.c create mode 100644 dlls/win32u/snidrv/snidrv.h
diff --git a/dlls/win32u/Makefile.in b/dlls/win32u/Makefile.in index 878baeaffbe..acf9f73fa89 100644 --- a/dlls/win32u/Makefile.in +++ b/dlls/win32u/Makefile.in @@ -3,8 +3,8 @@ MODULE = win32u.dll UNIXLIB = win32u.so IMPORTLIB = win32u IMPORTS = ntdll winecrt0 -UNIX_CFLAGS = $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) -UNIX_LIBS = $(CARBON_LIBS) $(APPKIT_LIBS) $(PTHREAD_LIBS) -lm +UNIX_CFLAGS = $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) $(DBUS_CFLAGS) +UNIX_LIBS = $(CARBON_LIBS) $(APPKIT_LIBS) $(PTHREAD_LIBS) $(DBUS_LIBS) -lm
EXTRADLLFLAGS = -nodefaultlibs
@@ -47,6 +47,8 @@ SOURCES = \ rawinput.c \ region.c \ scroll.c \ + snidrv/image.c \ + snidrv/dbus.c \ spy.c \ syscall.c \ sysparams.c \ diff --git a/dlls/win32u/snidrv/dbus.c b/dlls/win32u/snidrv/dbus.c new file mode 100644 index 00000000000..0634ec3cedc --- /dev/null +++ b/dlls/win32u/snidrv/dbus.c @@ -0,0 +1,1731 @@ +/* + * DBus tray support + * + * Copyright 2023 Sergei Chernyadyev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include "snidrv.h" + +#include <pthread.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <limits.h> +#include <dlfcn.h> +#include <poll.h> + +#include <dbus/dbus.h> + +#include "wine/list.h" +#include "wine/unixlib.h" +#include "wine/gdi_driver.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(winesni); + +#define DBUS_FUNCS \ + DO_FUNC(dbus_bus_add_match); \ + DO_FUNC(dbus_bus_get); \ + DO_FUNC(dbus_bus_get_private); \ + DO_FUNC(dbus_bus_add_match); \ + DO_FUNC(dbus_bus_remove_match); \ + DO_FUNC(dbus_bus_get_unique_name); \ + DO_FUNC(dbus_connection_add_filter); \ + DO_FUNC(dbus_connection_read_write); \ + DO_FUNC(dbus_connection_dispatch); \ + DO_FUNC(dbus_connection_get_dispatch_status); \ + DO_FUNC(dbus_connection_read_write_dispatch); \ + DO_FUNC(dbus_connection_remove_filter); \ + DO_FUNC(dbus_connection_send); \ + DO_FUNC(dbus_connection_send_with_reply); \ + DO_FUNC(dbus_connection_send_with_reply_and_block); \ + DO_FUNC(dbus_connection_flush); \ + DO_FUNC(dbus_connection_try_register_object_path); \ + DO_FUNC(dbus_connection_unregister_object_path); \ + DO_FUNC(dbus_connection_list_registered); \ + DO_FUNC(dbus_connection_close); \ + DO_FUNC(dbus_connection_ref); \ + DO_FUNC(dbus_connection_unref); \ + DO_FUNC(dbus_connection_get_object_path_data); \ + DO_FUNC(dbus_connection_get_unix_fd); \ + DO_FUNC(dbus_connection_set_watch_functions); \ + DO_FUNC(dbus_watch_get_unix_fd); \ + DO_FUNC(dbus_watch_handle); \ + DO_FUNC(dbus_watch_get_flags); \ + DO_FUNC(dbus_watch_get_enabled); \ + DO_FUNC(dbus_error_free); \ + DO_FUNC(dbus_error_init); \ + DO_FUNC(dbus_error_is_set); \ + DO_FUNC(dbus_set_error_from_message); \ + DO_FUNC(dbus_free_string_array); \ + DO_FUNC(dbus_message_get_args); \ + DO_FUNC(dbus_message_get_interface); \ + DO_FUNC(dbus_message_get_member); \ + DO_FUNC(dbus_message_get_path); \ + DO_FUNC(dbus_message_get_type); \ + DO_FUNC(dbus_message_is_signal); \ + DO_FUNC(dbus_message_iter_append_basic); \ + DO_FUNC(dbus_message_iter_get_arg_type); \ + DO_FUNC(dbus_message_iter_get_basic); \ + DO_FUNC(dbus_message_iter_append_fixed_array); \ + DO_FUNC(dbus_message_iter_get_fixed_array); \ + DO_FUNC(dbus_message_iter_init); \ + DO_FUNC(dbus_message_iter_init_append); \ + DO_FUNC(dbus_message_iter_next); \ + DO_FUNC(dbus_message_iter_recurse); \ + DO_FUNC(dbus_message_iter_open_container); \ + DO_FUNC(dbus_message_iter_close_container); \ + DO_FUNC(dbus_message_iter_abandon_container_if_open); \ + DO_FUNC(dbus_message_new_method_return); \ + DO_FUNC(dbus_message_new_method_call); \ + DO_FUNC(dbus_message_new_signal); \ + DO_FUNC(dbus_message_is_method_call); \ + DO_FUNC(dbus_message_new_error); \ + DO_FUNC(dbus_pending_call_block); \ + DO_FUNC(dbus_pending_call_unref); \ + DO_FUNC(dbus_pending_call_steal_reply); \ + DO_FUNC(dbus_threads_init_default); \ + DO_FUNC(dbus_message_unref) + +#define DO_FUNC(f) static typeof(f) * p_##f +DBUS_FUNCS; +#undef DO_FUNC + +/* an individual systray icon */ +struct tray_icon +{ + struct list entry; + HWND owner; /* the HWND passed in to the Shell_NotifyIcon call */ + HICON hIcon; + void* icon_bitmap; + UINT icon_width; + UINT icon_height; + UINT state; /* state flags */ + UINT id; /* the unique id given by the app */ + UINT callback_message; + char tiptext[128 * 3]; /* tooltip text */ + UINT version; /* notify icon api version */ + unsigned int notification_id; + DBusConnection* connection; + DBusWatch* watch; + int watch_fd; + UINT watch_flags; + pthread_mutex_t mutex; /* mutex */ +}; + +static pthread_once_t init_control = PTHREAD_ONCE_INIT; + +static struct list sni_list = LIST_INIT( sni_list ); + +static pthread_mutex_t list_mutex; + +struct standalone_notification { + struct list entry; + + HWND owner; + UINT id; + unsigned int notification_id; +}; + +static struct list standalone_notification_list = LIST_INIT( standalone_notification_list ); + +static pthread_mutex_t standalone_notifications_mutex = PTHREAD_MUTEX_INITIALIZER; + + +#define BALLOON_SHOW_MIN_TIMEOUT 10000 +#define BALLOON_SHOW_MAX_TIMEOUT 30000 + +static void* dbus_module = NULL; +static const char* kde_watcher_interface_name = "org.kde.StatusNotifierWatcher"; +static const char* freedesktop_watcher_interface_name = "org.freedesktop.StatusNotifierWatcher"; +static const char* watcher_interface_name = "org.kde.StatusNotifierWatcher"; + +static const char* kde_item_interface_name = "org.kde.StatusNotifierItem"; +static const char* freedesktop_item_interface_name = "org.freedesktop.StatusNotifierItem"; +static const char* item_interface_name = "org.kde.StatusNotifierItem"; + +static const char* notifications_interface_name = "org.freedesktop.Notifications"; + +static DBusConnection *global_connection; +static DBusWatch *global_connection_watch; +static int global_connection_watch_fd; +static UINT global_connection_watch_flags; +static BOOL sni_initialized = FALSE; +static BOOL notifications_initialized = FALSE; + +static char* status_notifier_dst_path = NULL; +static char* notifications_dst_path = NULL; + +static const char *status_field = "Status"; +static const char *icon_field = "IconPixmap"; +static const char *icon_name_field = "IconName"; +static const char *title_field = "Title"; +static const char *category_field = "Category"; +static const char *id_field = "Id"; + +static BOOL get_notifier_watcher_owner(void); + +static BOOL load_dbus_functions(void) +{ + if (!(dbus_module = dlopen( SONAME_LIBDBUS_1, RTLD_NOW ))) + goto failed; + +#define DO_FUNC(f) if (!(p_##f = dlsym( dbus_module, #f ))) goto failed + DBUS_FUNCS; +#undef DO_FUNC + return TRUE; + +failed: + WARN( "failed to load DBUS support: %s\n", dlerror() ); + return FALSE; +} + +static void sni_finalize(void) +{ + pthread_mutex_destroy(&list_mutex); + free(status_notifier_dst_path); +} + +static void notifications_finalize(void) +{ + free(notifications_dst_path); +} + +static void dbus_finalize(void) +{ + if (global_connection != NULL) + { + p_dbus_connection_flush(global_connection); + p_dbus_connection_close(global_connection); + p_dbus_connection_unref(global_connection); + } + if (dbus_module != NULL) + { + dlclose(dbus_module); + } +} + +static dbus_bool_t add_watch(DBusWatch *w, void *data); +static void remove_watch(DBusWatch *w, void *data); +static void toggle_watch(DBusWatch *w, void *data); + +static BOOL notifications_initialize(void); + +static BOOL dbus_initialize(void) +{ + DBusError error; + p_dbus_error_init( &error ); + if (!p_dbus_threads_init_default()) return FALSE; + if (!(global_connection = p_dbus_bus_get_private( DBUS_BUS_SESSION, &error ))) + { + WARN("failed to get system dbus connection: %s\n", error.message ); + p_dbus_error_free( &error ); + return FALSE; + } + + if (!p_dbus_connection_set_watch_functions(global_connection, add_watch, remove_watch, + toggle_watch, NULL, NULL)) + { + WARN("dbus_set_watch_functions() failed\n"); + return FALSE; + } + return TRUE; +} + +static void snidrv_once_initialize(void) +{ + if (!load_dbus_functions()) goto err; + if (!dbus_initialize()) goto err; + if (get_notifier_watcher_owner()) + /* TODO: replace this with Interlocked if there will be a getter function for this variable */ + sni_initialized = TRUE; + if (notifications_initialize()) + notifications_initialized = TRUE; +err: + if (!sni_initialized) + sni_finalize(); + if (!notifications_initialized) + notifications_finalize(); + if (!sni_initialized && !notifications_initialized) + dbus_finalize(); +} + +BOOL snidrv_init(void) +{ + pthread_once(&init_control, snidrv_once_initialize); + return sni_initialized; +} + +BOOL snidrv_notification_init(void) +{ + pthread_once(&init_control, snidrv_once_initialize); + return notifications_initialized; +} + +static dbus_bool_t add_watch(DBusWatch *w, void *data) +{ + int fd; + unsigned int flags, poll_flags; + if (!p_dbus_watch_get_enabled(w)) + return TRUE; + + fd = p_dbus_watch_get_unix_fd(w); + flags = p_dbus_watch_get_flags(w); + poll_flags = 0; + + if (flags & DBUS_WATCH_READABLE) + poll_flags |= POLLIN; + if (flags & DBUS_WATCH_WRITABLE) + poll_flags |= POLLOUT; + + pthread_mutex_lock(&list_mutex); + if (data != NULL) + { + struct tray_icon *icon = (struct tray_icon *) data; + icon->watch_fd = fd; + icon->watch_flags = poll_flags; + icon->watch = w; + } + else + { + /* global connection */ + global_connection_watch_fd = fd; + global_connection_watch_flags = poll_flags; + global_connection_watch = w; + } + pthread_mutex_unlock(&list_mutex); + + return TRUE; +} + +static void remove_watch(DBusWatch *w, void *data) +{ + pthread_mutex_lock(&list_mutex); + if (data != NULL) + { + struct tray_icon *icon = (struct tray_icon *) data; + icon->watch_fd = 0; + icon->watch_flags = 0; + icon->watch = NULL; + } + else + { + /* global connection */ + global_connection_watch_fd = 0; + global_connection_watch_flags = 0; + global_connection_watch = NULL; + } + pthread_mutex_unlock(&list_mutex); +} + + +static void toggle_watch(DBusWatch *w, void *data) +{ + if (p_dbus_watch_get_enabled(w)) + add_watch(w, data); + else + remove_watch(w, data); +} + +static const char* dbus_name_owning_match = "type='signal'," + "interface='org.freedesktop.DBus'," + "sender='org.freedesktop.DBus'," + "member='NameOwnerChanged'"; + +static const char* dbus_notification_close_signal = "type='signal'," + "interface='org.freedesktop.Notifications'," + "member='NotificationClosed'"; + +static const char* const object_path = "/StatusNotifierItem"; + +static BOOL register_notification_item(DBusConnection* ctx); + +static void restore_items(DBusConnection *ctx) +{ + struct tray_icon *icon; + + LIST_FOR_EACH_ENTRY( icon, &sni_list, struct tray_icon, entry ) + register_notification_item(icon->connection); +} + +static DBusHandlerResult name_owner_filter( DBusConnection *ctx, DBusMessage *msg, void *user_data ) +{ + char *interface_name, *old_path, *new_path; + DBusError error; + + p_dbus_error_init( &error ); + + if (p_dbus_message_is_signal( msg, "org.freedesktop.DBus", "NameOwnerChanged" ) && + p_dbus_message_get_args( msg, &error, DBUS_TYPE_STRING, &interface_name, DBUS_TYPE_STRING, &old_path, + DBUS_TYPE_STRING, &new_path, DBUS_TYPE_INVALID )) + { + bool is_kde_interface = strcmp(interface_name, kde_watcher_interface_name) == 0; + bool is_freedesktop_interface = strcmp(interface_name, freedesktop_watcher_interface_name) == 0; + /* check if watcher is disabled first*/ + if (is_kde_interface || is_freedesktop_interface) + { + /* TODO: lock the mutex */ + if (status_notifier_dst_path == NULL || status_notifier_dst_path[0] == '\0') + { + pthread_mutex_lock(&list_mutex); + /* switch between KDE and freedesktop interfaces, despite most implementations rely on KDE interface names */ + if (is_kde_interface) + { + watcher_interface_name = kde_watcher_interface_name; + item_interface_name = kde_item_interface_name; + } + else if (is_freedesktop_interface) + { + watcher_interface_name = freedesktop_watcher_interface_name; + item_interface_name = freedesktop_item_interface_name; + } + + old_path = status_notifier_dst_path; + status_notifier_dst_path = strdup(new_path); + free(old_path); + + if (status_notifier_dst_path != NULL && status_notifier_dst_path[0] != '\0') + restore_items(ctx); + + pthread_mutex_unlock(&list_mutex); + } + else if (status_notifier_dst_path != NULL && + status_notifier_dst_path[0] != '\0' && + strcmp(interface_name, watcher_interface_name) == 0) + { + pthread_mutex_lock(&list_mutex); + old_path = status_notifier_dst_path; + status_notifier_dst_path = strdup(new_path); + free(old_path); + pthread_mutex_lock(&list_mutex); + } + } + else if (strcmp(interface_name, notifications_interface_name) == 0) + { + struct standalone_notification *this, *next; + pthread_mutex_lock(&standalone_notifications_mutex); + old_path = notifications_dst_path; + notifications_dst_path = strdup(new_path); + free(old_path); + + LIST_FOR_EACH_ENTRY_SAFE( this, next, &standalone_notification_list, struct standalone_notification, entry ) + { + list_remove(&this->entry); + free(this); + } + pthread_mutex_unlock(&standalone_notifications_mutex); + } + } + else if (p_dbus_message_is_signal( msg, notifications_interface_name, "NotificationClosed" )) + { + unsigned int id, reason; + struct standalone_notification *this, *next; + if (!p_dbus_message_get_args( msg, &error, DBUS_TYPE_UINT32, &id, DBUS_TYPE_UINT32, &reason )) + goto cleanup; + pthread_mutex_lock(&standalone_notifications_mutex); + /* TODO: clear the list */ + LIST_FOR_EACH_ENTRY_SAFE( this, next, &standalone_notification_list, struct standalone_notification, entry ) + { + if (this->notification_id == id) + { + list_remove(&this->entry); + free(this); + } + } + pthread_mutex_unlock(&standalone_notifications_mutex); + } + +cleanup: + p_dbus_error_free( &error ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static BOOL get_owner_for_interface(DBusConnection* connection, const char* interface_name, char** owner_path) +{ + DBusMessage* msg = NULL; + DBusMessageIter args; + DBusPendingCall* pending; + DBusError error; + char* status_notifier_dest = NULL; + p_dbus_error_init( &error ); + msg = p_dbus_message_new_method_call("org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetNameOwner"); + if (!msg) goto err; + + p_dbus_message_iter_init_append(msg, &args); + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &interface_name )) goto err; + if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1)) goto err; + if (!pending) goto err; + + p_dbus_message_unref(msg); + + p_dbus_pending_call_block(pending); + + msg = p_dbus_pending_call_steal_reply(pending); + p_dbus_pending_call_unref(pending); + if (!msg) goto err; + + if (p_dbus_set_error_from_message (&error, msg)) + { + WARN("failed to query an owner - %s: %s\n", error.name, error.message); + p_dbus_error_free( &error); + goto err; + } + else if (!p_dbus_message_get_args( msg, &error, DBUS_TYPE_STRING, &status_notifier_dest, + DBUS_TYPE_INVALID )) + { + WARN("failed to get a response - %s: %s\n", error.name, error.message); + p_dbus_error_free( &error ); + goto err; + } + *owner_path = strdup(status_notifier_dest); + p_dbus_message_unref(msg); + return TRUE; +err: + p_dbus_message_unref(msg); + return FALSE; +} + +static BOOL get_notifier_watcher_owner_for_interface(DBusConnection* connection, const char* interface_name, const char* sni_interface_name) +{ + if (!get_owner_for_interface(connection, interface_name, &status_notifier_dst_path)) + return FALSE; + TRACE("found notifier destination name %s\n", status_notifier_dst_path); + watcher_interface_name = interface_name; + item_interface_name = sni_interface_name; + return TRUE; +} + +static BOOL get_notifier_watcher_owner(void) +{ + DBusError error; + pthread_mutexattr_t attr; + p_dbus_error_init( &error ); + + pthread_mutexattr_init( &attr ); + pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); + pthread_mutex_init( &list_mutex, &attr ); + + if (!get_notifier_watcher_owner_for_interface(global_connection, kde_watcher_interface_name, kde_item_interface_name) && + !get_notifier_watcher_owner_for_interface(global_connection, freedesktop_watcher_interface_name, freedesktop_item_interface_name)) + { + WARN("failed to query watcher interface owner\n"); + goto err; + } + + p_dbus_connection_add_filter( global_connection, name_owner_filter, NULL, NULL ); + p_dbus_bus_add_match( global_connection, dbus_name_owning_match, &error ); + p_dbus_bus_add_match( global_connection, dbus_notification_close_signal, &error ); + if (p_dbus_error_is_set(&error)) + { + WARN("failed to register matcher %s: %s\n", error.name, error.message); + p_dbus_error_free( &error); + goto err; + } + pthread_mutexattr_destroy( &attr ); + return TRUE; + +err: + pthread_mutexattr_destroy( &attr ); + return FALSE; +} + +static BOOL notifications_initialize(void) +{ + char* dst_path = NULL; + BOOL ret = get_owner_for_interface(global_connection, "org.freedesktop.Notifications", &dst_path); + notifications_dst_path = dst_path; + return ret; +} + +static BOOL handle_notification_icon(DBusMessageIter *iter, const unsigned char* icon_bits, unsigned width, unsigned height) +{ + DBusMessageIter sIter,bIter; + unsigned row_stride = width * 4; + const unsigned channel_count = 4; + const unsigned bits_per_sample = 8; + const bool has_alpha = true; + if (!p_dbus_message_iter_open_container(iter, 'r', NULL, &sIter)) + { + WARN("Failed to open struct inside array!\n"); + goto fail; + } + + p_dbus_message_iter_append_basic(&sIter, 'i', &width); + p_dbus_message_iter_append_basic(&sIter, 'i', &height); + p_dbus_message_iter_append_basic(&sIter, 'i', &row_stride); + p_dbus_message_iter_append_basic(&sIter, 'b', &has_alpha); + p_dbus_message_iter_append_basic(&sIter, 'i', &bits_per_sample); + p_dbus_message_iter_append_basic(&sIter, 'i', &channel_count); + + if (p_dbus_message_iter_open_container(&sIter, 'a', DBUS_TYPE_BYTE_AS_STRING, &bIter)) + { + p_dbus_message_iter_append_fixed_array(&bIter, DBUS_TYPE_BYTE, &icon_bits, width * height * 4); + p_dbus_message_iter_close_container(&sIter, &bIter); + } + else + { + p_dbus_message_iter_abandon_container_if_open(iter, &sIter); + goto fail; + } + p_dbus_message_iter_close_container(iter, &sIter); + return TRUE; +fail: + return FALSE; +} + +static BOOL close_notification(DBusConnection* connection, UINT id) +{ + BOOL ret = FALSE; + + DBusMessage* msg = NULL; + DBusMessageIter args; + DBusPendingCall* pending; + DBusError error; + + p_dbus_error_init( &error ); + msg = p_dbus_message_new_method_call(notifications_dst_path, + "/org/freedesktop/Notifications", + notifications_interface_name, + "CloseNotification"); + if (!msg) goto err; + p_dbus_message_iter_init_append(msg, &args); + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &id )) goto err; + + if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1)) + goto err; + + if (!pending) goto err; + + p_dbus_message_unref(msg); + + p_dbus_pending_call_block(pending); + + msg = p_dbus_pending_call_steal_reply(pending); + p_dbus_pending_call_unref(pending); + if (!msg) goto err; + if (p_dbus_set_error_from_message (&error, msg)) + { + WARN("got an error - %s: %s\n", error.name, error.message); + p_dbus_error_free( &error); + } + ret = TRUE; +err: + p_dbus_message_unref(msg); + + return ret; +} + +static BOOL send_notification(DBusConnection* connection, UINT id, const WCHAR* title, const WCHAR* text, HICON icon, UINT info_flags, UINT timeout, unsigned int *p_new_id) +{ + char info_text[256 * 3]; + char info_title[128 * 3]; + const char *info_text_ptr = info_text, *info_title_ptr = info_title; + const char* empty_string = ""; + const char* icon_name = ""; + BOOL ret = FALSE; + DBusMessage* msg = NULL; + DBusMessageIter args, aIter, eIter, vIter; + DBusPendingCall* pending; + DBusError error; + /* icon */ + void* icon_bits = NULL; + unsigned width, height; + HICON new_icon = NULL; + int expire_timeout; + /* no text for balloon, so no balloon */ + if (!text || !text[0]) + return TRUE; + + info_title[0] = 0; + info_text[0] = 0; + if (title) ntdll_wcstoumbs(title, wcslen(title) + 1, info_title, ARRAY_SIZE(info_title), FALSE); + if (text) ntdll_wcstoumbs(text, wcslen(text) + 1, info_text, ARRAY_SIZE(info_text), FALSE); + /*icon*/ + if ((info_flags & NIIF_ICONMASK) == NIIF_USER && icon) + { + unsigned int *u_icon_bits; + new_icon = CopyImage(icon, IMAGE_ICON, 0, 0, 0); + if (!create_bitmap_from_icon(new_icon, &width, &height, &icon_bits)) + { + WARN("failed to copy icon %p\n", new_icon); + goto err; + } + u_icon_bits = icon_bits; + /* convert to RGBA, turns out that unlike tray icons it needs RGBA */ + for (unsigned i = 0; i < width * height; i++) + { +#ifdef WORDS_BIGENDIAN + u_icon_bits[i] = (u_icon_bits[i] << 8) | (u_icon_bits[i] >> 24); +#else + u_icon_bits[i] = (u_icon_bits[i] << 24) | (u_icon_bits[i] >> 8); +#endif + } + } + else + { + /* show placeholder icons */ + switch (info_flags & NIIF_ICONMASK) + { + case NIIF_INFO: + icon_name = "dialog-information"; + break; + case NIIF_WARNING: + icon_name = "dialog-warning"; + break; + case NIIF_ERROR: + icon_name = "dialog-error"; + break; + default: + break; + } + } + p_dbus_error_init( &error ); + msg = p_dbus_message_new_method_call(notifications_dst_path, + "/org/freedesktop/Notifications", + notifications_interface_name, + "Notify"); + if (!msg) goto err; + + p_dbus_message_iter_init_append(msg, &args); + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &empty_string )) + goto err; + /* override id */ + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &id )) + goto err; + /* icon name */ + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &icon_name )) + goto err; + /* title */ + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info_title_ptr )) + goto err; + /* body */ + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info_text_ptr )) + goto err; + /* actions */ + /* TODO: add default action */ + if (p_dbus_message_iter_open_container(&args, 'a', DBUS_TYPE_STRING_AS_STRING, &aIter)) + p_dbus_message_iter_close_container(&args, &aIter); + else + goto err; + + /* hints */ + if (p_dbus_message_iter_open_container(&args, 'a', "{sv}", &aIter)) + { + if ((info_flags & NIIF_ICONMASK) == NIIF_USER && icon) + { + const char* icon_data_field = "image-data"; + if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter)) + { + p_dbus_message_iter_abandon_container_if_open(&args, &aIter); + goto err; + } + + p_dbus_message_iter_append_basic(&eIter, 's', &icon_data_field); + + if (!p_dbus_message_iter_open_container(&eIter, 'v', "(iiibiiay)", &vIter)) + { + p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter); + p_dbus_message_iter_abandon_container_if_open(&args, &aIter); + goto err; + } + + if (!handle_notification_icon(&vIter, icon_bits, width, height)) + { + p_dbus_message_iter_abandon_container_if_open(&eIter, &vIter); + p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter); + p_dbus_message_iter_abandon_container_if_open(&args, &aIter); + goto err; + } + + p_dbus_message_iter_close_container(&eIter, &vIter); + p_dbus_message_iter_close_container(&aIter, &eIter); + } + p_dbus_message_iter_close_container(&args, &aIter); + } + else + goto err; + if (timeout == 0) + /* just set it to system default */ + expire_timeout = -1; + else + expire_timeout = max(min(timeout, BALLOON_SHOW_MAX_TIMEOUT), BALLOON_SHOW_MIN_TIMEOUT); + + /* timeout */ + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, &expire_timeout )) + goto err; + if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1)) + goto err; + if (!pending) goto err; + + p_dbus_message_unref(msg); + + p_dbus_pending_call_block(pending); + + msg = p_dbus_pending_call_steal_reply(pending); + p_dbus_pending_call_unref(pending); + if (!msg) goto err; + + if (p_dbus_set_error_from_message (&error, msg)) + { + WARN("failed to create a notification - %s: %s\n", error.name, error.message); + p_dbus_error_free( &error); + goto err; + } + if (!p_dbus_message_iter_init(msg, &args)) + goto err; + + if (DBUS_TYPE_UINT32 != p_dbus_message_iter_get_arg_type(&args)) + goto err; + else if (p_new_id) + p_dbus_message_iter_get_basic(&args, p_new_id); + ret = TRUE; +err: + p_dbus_message_unref(msg); + if (new_icon) NtUserDestroyCursor(new_icon, 0); + free(icon_bits); + return ret; +} + +static BOOL handle_id(DBusConnection* conn, DBusMessageIter *iter, const struct tray_icon* icon) +{ + char id[64]; + const char* id_ptr = id; + snprintf(id, 64, "wine_tray_%p_%d", (void*)icon->owner, icon->id); + p_dbus_message_iter_append_basic(iter, 's', &id_ptr); + return true; +} + +static bool handle_icon_name(DBusConnection* conn, DBusMessageIter *iter) +{ + const char* icon_name = "wine_tray_icon"; + return p_dbus_message_iter_append_basic(iter, 's', &icon_name); +} + +static BOOL handle_icon(DBusConnection* conn, DBusMessageIter *iter, const struct tray_icon* icon) +{ + DBusMessageIter aIter, sIter,bIter; + if (!p_dbus_message_iter_open_container(iter, 'a', "(iiay)", &aIter)) + { + WARN("Failed to open array!\n"); + goto fail; + } + + if (!p_dbus_message_iter_open_container(&aIter, 'r', NULL, &sIter)) + { + WARN("Failed to open struct inside array!\n"); + p_dbus_message_iter_abandon_container_if_open(iter, &aIter); + goto fail; + } + + p_dbus_message_iter_append_basic(&sIter, 'i', &icon->icon_width); + p_dbus_message_iter_append_basic(&sIter, 'i', &icon->icon_height); + + if (p_dbus_message_iter_open_container(&sIter, 'a', DBUS_TYPE_BYTE_AS_STRING, &bIter)) + { + p_dbus_message_iter_append_fixed_array(&bIter, DBUS_TYPE_BYTE, &icon->icon_bitmap, icon->icon_width * icon->icon_height * 4); + p_dbus_message_iter_close_container(&sIter, &bIter); + } + else + goto fail; + + p_dbus_message_iter_close_container(&aIter, &sIter); + p_dbus_message_iter_close_container(iter, &aIter); + return TRUE; +fail: + return FALSE; +} + +static void handle_title(DBusConnection* conn, DBusMessageIter *iter, const struct tray_icon* icon) +{ + const char* tiptext = (const char*)icon->tiptext; + p_dbus_message_iter_append_basic(iter, 's', &tiptext); +} + +static void handle_category(DBusConnection* conn, DBusMessageIter *iter) +{ + const char *cat = "ApplicationStatus"; + p_dbus_message_iter_append_basic(iter, 's', &cat); +} + +static void handle_status(DBusConnection* conn, DBusMessageIter *iter, const struct tray_icon* icon) +{ + const char *status = (icon->state & NIS_HIDDEN) ? "Passive" : "Active"; + p_dbus_message_iter_append_basic(iter, 's', &status); +} + +static BOOL notify_owner( const struct tray_icon *icon, UINT msg, unsigned short x, unsigned short y) +{ + WPARAM wp = icon->id; + LPARAM lp = msg; + + if (icon->version >= NOTIFYICON_VERSION_4) + { + POINT pt = { x, y }; + + wp = MAKEWPARAM( pt.x, pt.y ); + lp = MAKELPARAM( msg, icon->id ); + } + + TRACE( "relaying 0x%x\n", msg ); + if (!NtUserMessageCall( icon->owner, icon->callback_message, wp, lp, 0, NtUserSendNotifyMessage, FALSE )) + { + WARN( "application window was destroyed, removing icon %u\n", icon->id ); + /* TODO: delete the icon */ + return FALSE; + } + return TRUE; +} + +static DBusHandlerResult notification_send_error(DBusConnection *conn, DBusMessage *message, const char* error, const char* message_text) +{ + DBusMessage* reply; + unsigned serial = 0; + reply = p_dbus_message_new_error (message, error, message_text); + if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (!p_dbus_connection_send(conn, reply, &serial)) + { + p_dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + p_dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +} + +DBusHandlerResult get_all_tray_properties(DBusConnection* conn, DBusMessage *message, const struct tray_icon* icon) +{ + DBusMessage* reply; + DBusMessageIter iter, aIter, vIter; + DBusMessageIter eIter; + unsigned serial = 0; + reply = p_dbus_message_new_method_return(message); + if (!reply) goto fail; + + p_dbus_message_iter_init_append(reply, &iter); + pthread_mutex_lock((pthread_mutex_t*)&icon->mutex); + + if (!p_dbus_message_iter_open_container(&iter, 'a', "{sv}", &aIter)) goto fail; + + if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter)) + { + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + p_dbus_message_iter_append_basic(&eIter, 's', &id_field); + + if (!p_dbus_message_iter_open_container(&eIter, 'v', "s", &vIter)) + { + p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter); + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + handle_id(conn, &vIter, icon); + + p_dbus_message_iter_close_container(&eIter, &vIter); + p_dbus_message_iter_close_container(&aIter, &eIter); + + if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter)) + { + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + p_dbus_message_iter_append_basic(&eIter, 's', &category_field); + + if (!p_dbus_message_iter_open_container(&eIter, 'v', "s", &vIter)) + { + p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter); + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + handle_category(conn, &vIter); + + p_dbus_message_iter_close_container(&eIter, &vIter); + p_dbus_message_iter_close_container(&aIter, &eIter); + /* Title */ + if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter)) + { + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + p_dbus_message_iter_append_basic(&eIter, 's', &title_field); + + if (!p_dbus_message_iter_open_container(&eIter, 'v', "s", &vIter)) + { + p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter); + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + handle_title(conn, &vIter, icon); + + p_dbus_message_iter_close_container(&eIter, &vIter); + p_dbus_message_iter_close_container(&aIter, &eIter); + /* status */ + if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter)) + { + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + p_dbus_message_iter_append_basic(&eIter, 's', &status_field); + + if (!p_dbus_message_iter_open_container(&eIter, 'v', "s", &vIter)) + { + p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter); + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + handle_status(conn, &vIter, icon); + + p_dbus_message_iter_close_container(&eIter, &vIter); + p_dbus_message_iter_close_container(&aIter, &eIter); + + if (icon->icon_bitmap != NULL) + { + /* Icon */ + if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter)) + { + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + p_dbus_message_iter_append_basic(&eIter, 's', &icon_field); + + if (!p_dbus_message_iter_open_container(&eIter, 'v', "a(iiay)", &vIter)) + { + WARN("failed to create iconpixmap array\n"); + p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter); + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + handle_icon(conn, &vIter, icon); + + p_dbus_message_iter_close_container(&eIter, &vIter); + p_dbus_message_iter_close_container(&aIter, &eIter); + } + + if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter)) + { + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + p_dbus_message_iter_append_basic(&eIter, 's', &icon_name_field); + + if (!p_dbus_message_iter_open_container(&eIter, 'v', "s", &vIter)) + { + WARN("failed to create icon name value\n"); + p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter); + p_dbus_message_iter_abandon_container_if_open(&iter, &aIter); + goto fail; + } + + handle_icon_name(conn, &vIter); + + p_dbus_message_iter_close_container(&eIter, &vIter); + p_dbus_message_iter_close_container(&aIter, &eIter); + p_dbus_message_iter_close_container(&iter, &aIter); + + pthread_mutex_unlock((pthread_mutex_t*)&icon->mutex); + + if (!p_dbus_connection_send(conn, reply, &serial)) + goto send_fail; + + p_dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +fail: + pthread_mutex_unlock((pthread_mutex_t*)&icon->mutex); +send_fail: + p_dbus_message_unref(reply); + + return notification_send_error(conn, message, "org.freedesktop.DBus.Error.Failed", "got an error while processing properties"); +} + +DBusHandlerResult notification_message_handler(DBusConnection *conn, DBusMessage *message, void *data) +{ + const struct tray_icon* icon = (struct tray_icon*)data; + if (p_dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Get")) + { + const char* interface_name = ""; + const char* property_name = ""; + DBusMessage* reply = NULL; + DBusMessageIter iter, vIter; + unsigned serial = 0; + DBusHandlerResult ret = DBUS_HANDLER_RESULT_HANDLED; + DBusError error; + p_dbus_error_init(&error); + if (!p_dbus_message_get_args( message, &error, DBUS_TYPE_STRING, &interface_name, + DBUS_TYPE_STRING, &property_name, DBUS_TYPE_INVALID )) + { + DBusHandlerResult ret = notification_send_error (conn, message, error.name, error.message); + p_dbus_error_free( &error); + return ret; + } + + if (strcmp(interface_name, freedesktop_item_interface_name) != 0 && + strcmp(interface_name, kde_item_interface_name) != 0) + { + char error_message[128]; + snprintf(error_message, sizeof(error_message), "unsupported interface %s", interface_name); + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.UnknownProperty", error_message); + } + + pthread_mutex_lock((pthread_mutex_t*)&icon->mutex); + + if (strcmp(property_name, title_field) == 0) + { + reply = p_dbus_message_new_method_return(message); + p_dbus_message_iter_init_append(reply, &iter); + if (!p_dbus_message_iter_open_container(&iter, 'v', "s", &vIter)) + { + p_dbus_message_unref(reply); + ret = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto err_get; + } + handle_title(conn, &vIter, icon); + p_dbus_message_iter_close_container(&iter, &vIter); + } + else if (strcmp(property_name, id_field) == 0) + { + reply = p_dbus_message_new_method_return(message); + p_dbus_message_iter_init_append(reply, &iter); + if (!p_dbus_message_iter_open_container(&iter, 'v', "s", &vIter)) + { + p_dbus_message_unref(reply); + ret = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto err_get; + } + handle_id(conn, &vIter, icon); + p_dbus_message_iter_close_container(&iter, &vIter); + } + else if (strcmp(property_name, icon_field) == 0 && icon->hIcon) + { + reply = p_dbus_message_new_method_return(message); + p_dbus_message_iter_init_append(reply, &iter); + if (!p_dbus_message_iter_open_container(&iter, 'v', "a(iiay)", &vIter)) + { + p_dbus_message_unref(reply); + ret = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto err_get; + } + handle_icon(conn, &vIter, icon); + p_dbus_message_iter_close_container(&iter, &vIter); + } + else if (strcmp(property_name, status_field) == 0) + { + reply = p_dbus_message_new_method_return(message); + p_dbus_message_iter_init_append(reply, &iter); + if (!p_dbus_message_iter_open_container(&iter, 'v', "s", &vIter)) + { + p_dbus_message_unref(reply); + ret = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto err_get; + } + handle_status(conn, &vIter, icon); + p_dbus_message_iter_close_container(&iter, &vIter); + } + else + { + char error_message[128]; + pthread_mutex_unlock((pthread_mutex_t*)&icon->mutex); + snprintf(error_message, sizeof(error_message), "interface doesn't have the property %s", property_name); + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.UnknownProperty", error_message); + } + + if (!p_dbus_connection_send(conn, reply, &serial)) + { + p_dbus_message_unref(reply); + ret = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto err_get; + } + + p_dbus_message_unref(reply); + err_get: + pthread_mutex_unlock((pthread_mutex_t*)&icon->mutex); + return ret; + } + else if (p_dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "GetAll")) + { + const char* interface_name = ""; + DBusMessageIter args; + if (!p_dbus_message_iter_init(message, &args)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + else if (DBUS_TYPE_STRING != p_dbus_message_iter_get_arg_type(&args)) + { + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + } + else + p_dbus_message_iter_get_basic(&args, &interface_name); + + if (strcmp(kde_item_interface_name, interface_name) == 0 || + strcmp(freedesktop_item_interface_name, interface_name) == 0) + return get_all_tray_properties(conn, message, icon); + else + return notification_send_error(conn, message, "org.freedesktop.DBus.Error.UnknownInterface", "Call to Get has wrong args"); + + } + else if (p_dbus_message_is_method_call(message, kde_item_interface_name, "ContextMenu") || + p_dbus_message_is_method_call(message, freedesktop_item_interface_name, "ContextMenu")) + { + int x,y; + DBusMessageIter args; + if (!p_dbus_message_iter_init(message, &args)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &x); + + if (!p_dbus_message_iter_next(&args) || DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &y); + + notify_owner( icon, WM_RBUTTONDOWN, (unsigned short) x, (unsigned short) y); + if (icon->version > 0) + notify_owner( icon, WM_CONTEXTMENU, (unsigned short) x, (unsigned short) y); + } + else if (p_dbus_message_is_method_call(message, kde_item_interface_name, "Activate") || + p_dbus_message_is_method_call(message, freedesktop_item_interface_name, "Activate")) + { + int x,y; + DBusMessageIter args; + if (!p_dbus_message_iter_init(message, &args)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &x); + + if (!p_dbus_message_iter_next(&args) || DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &y); + + notify_owner( icon, WM_LBUTTONDOWN, (unsigned short) x, (unsigned short) y); + if (icon->version > 0) notify_owner( icon, NIN_SELECT, (unsigned short) x, (unsigned short) y); + } + else if (p_dbus_message_is_method_call(message, kde_item_interface_name, "SecondaryActivate") || + p_dbus_message_is_method_call(message, freedesktop_item_interface_name, "SecondaryActivate")) + { + int x,y; + DBusMessageIter args; + if (!p_dbus_message_iter_init(message, &args)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &x); + + if (!p_dbus_message_iter_next(&args) || DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &y); + notify_owner( icon, WM_MBUTTONDOWN, (unsigned short) x, (unsigned short) y); + } + else if (p_dbus_message_is_method_call(message, kde_item_interface_name, "Scroll") || + p_dbus_message_is_method_call(message, freedesktop_item_interface_name, "Scroll")) + { + /* do nothing */ + } + else if (p_dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL) + return notification_send_error (conn, message, "DBus.Error.UnknownMethod", "Unknown method"); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +const DBusObjectPathVTable notification_vtable = +{ + .message_function = notification_message_handler, +}; + +BOOL snidrv_run_loop() +{ + DBusConnection* conns[128]; + DBusWatch* watches[128]; + struct pollfd fd_info[128]; + int fd_count; + struct pollfd* fd_ptr = fd_info; + + while (true) + { + int i, poll_ret; + struct tray_icon* icon; + fd_count = 0; + pthread_mutex_lock(&list_mutex); + if (global_connection_watch_fd) + { + conns[fd_count] = p_dbus_connection_ref(global_connection); + watches[fd_count] = global_connection_watch; + fd_info[fd_count++] = (struct pollfd) { + .fd = global_connection_watch_fd, + .events = global_connection_watch_flags, + .revents = 0, + }; + } + LIST_FOR_EACH_ENTRY( icon, &sni_list, struct tray_icon, entry ) + { + if (fd_count >= 128) + break; + if (!icon->watch_fd) + continue; + conns[fd_count] = p_dbus_connection_ref(icon->connection); + watches[fd_count] = icon->watch; + fd_info[fd_count++] = (struct pollfd) { + .fd = icon->watch_fd, + .events = icon->watch_flags, + .revents = 0, + }; + } + pthread_mutex_unlock(&list_mutex); + + poll_ret = poll(fd_ptr, fd_count, 100); + if (poll_ret == 0) + continue; + if (poll_ret == -1) + { + ERR("fd poll error\n"); + continue; + } + for ( i = 0; i < fd_count; i++ ) + { + if (fd_info[i].revents & (POLLERR | POLLHUP | POLLNVAL)) continue; + if (fd_info[i].revents & POLLIN) { + p_dbus_watch_handle(watches[i], DBUS_WATCH_READABLE); + while ( p_dbus_connection_get_dispatch_status ( conns[i] ) == DBUS_DISPATCH_DATA_REMAINS ) + { + p_dbus_connection_dispatch ( conns[i] ) ; + } + } + if (fd_info[i].revents & POLLOUT) { + p_dbus_watch_handle(watches[i], DBUS_WATCH_WRITABLE); + } + } + for ( i = 0; i < fd_count; i++ ) + { + p_dbus_connection_unref(conns[i]); + } + } + + return 0; +} + +static struct tray_icon *get_icon(HWND owner, UINT id) +{ + struct tray_icon *this; + + pthread_mutex_lock(&list_mutex); + LIST_FOR_EACH_ENTRY( this, &sni_list, struct tray_icon, entry ) + { + if ((this->id == id) && (this->owner == owner)) + { + pthread_mutex_unlock(&list_mutex); + return this; + } + } + pthread_mutex_unlock(&list_mutex); + return NULL; +} + +static BOOL register_notification_item(DBusConnection* connection) +{ + DBusMessageIter args; + DBusPendingCall* pending; + DBusMessage* msg = NULL; + DBusError error; + const char* object_name = object_path; + char service_object_name[256]; + if (strcmp(watcher_interface_name, freedesktop_watcher_interface_name) == 0) + { + /* prepend source unique name */ + snprintf(service_object_name, sizeof(service_object_name), "%s%s", p_dbus_bus_get_unique_name(connection), object_path); + object_name = service_object_name; + } + p_dbus_error_init( &error ); + msg = p_dbus_message_new_method_call(status_notifier_dst_path, + "/StatusNotifierWatcher", + watcher_interface_name , + "RegisterStatusNotifierItem"); + if (!msg) goto err; + + p_dbus_message_iter_init_append(msg, &args); + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &object_name )) + goto err; + + if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1)) + goto err; + + p_dbus_message_unref(msg); + + p_dbus_pending_call_block(pending); + + msg = p_dbus_pending_call_steal_reply(pending); + if (NULL == msg) goto err; + + p_dbus_pending_call_unref(pending); + + if (p_dbus_set_error_from_message (&error, msg)) + { + WARN("got error %s: %s\n", error.name, error.message); + p_dbus_error_free( &error); + goto err; + } + + p_dbus_message_unref(msg); + return TRUE; +err: + if (msg != NULL) p_dbus_message_unref(msg); + return FALSE; +} + +static BOOL send_signal_to_item(DBusConnection* connection, const char* signal_name) +{ + DBusMessage* msg; + DBusError error; + unsigned serial = 0; + p_dbus_error_init( &error ); + msg = p_dbus_message_new_signal(object_path, + item_interface_name, + signal_name); + if (NULL == msg) return FALSE; + + if (!p_dbus_connection_send (connection, msg, &serial)) + { + p_dbus_message_unref(msg); + return FALSE; + } + + p_dbus_message_unref(msg); + + return TRUE; +} + + +BOOL get_icon_data(const NOTIFYICONDATAW* icon_data, struct tray_icon* dst) +{ + void* bits = NULL; + unsigned width, height; + HICON new_icon = NULL; + new_icon = CopyImage(icon_data->hIcon, IMAGE_ICON, 0, 0, 0); + if (!create_bitmap_from_icon(new_icon, &width, &height, &bits)) + goto fail; + + if (dst->hIcon) NtUserDestroyCursor(dst->hIcon, 0); + if (dst->icon_bitmap) free(dst->icon_bitmap); + dst->hIcon = new_icon; + dst->icon_bitmap = bits; + dst->icon_width = width; + dst->icon_height = height; + return TRUE; +fail: + NtUserDestroyCursor(new_icon, 0); + free(bits); + return FALSE; +} + +BOOL snidrv_add_notify_icon(const NOTIFYICONDATAW* icon_data) +{ + DBusConnection* connection = NULL; + struct tray_icon* icon = NULL; + DBusError error; + bool registered = false; + p_dbus_error_init( &error ); + + if (!(connection = p_dbus_bus_get_private( DBUS_BUS_SESSION, &error ))) + { + WARN("failed to get system dbus connection: %s\n", error.message ); + p_dbus_error_free( &error ); + goto fail; + } + + icon = malloc(sizeof(struct tray_icon)); + if (!icon) goto fail; + + memset(icon, 0, sizeof(*icon)); + icon->id = icon_data->uID; + icon->owner = icon_data->hWnd; + icon->connection = connection; + if (pthread_mutex_init(&icon->mutex, NULL)) + { + WARN("failed to initialize mutex\n" ); + goto fail; + } + + if (!p_dbus_connection_set_watch_functions(connection, add_watch, remove_watch, + toggle_watch, icon, NULL)) { + WARN("dbus_set_watch_functions() failed\n"); + goto fail; + } + + if (icon_data->uFlags & NIF_ICON) + { + if (!get_icon_data(icon_data, icon)) + { + WARN("failed to get icon info\n" ); + goto fail; + } + } + if (icon_data->uFlags & NIF_MESSAGE) + { + icon->callback_message = icon_data->uCallbackMessage; + } + if (icon_data->uFlags & NIF_TIP) + ntdll_wcstoumbs(icon_data->szTip, wcslen(icon_data->szTip) + 1, icon->tiptext, ARRAY_SIZE(icon->tiptext), FALSE); + if (icon_data->uFlags & NIF_STATE) + icon->state = (icon->state & ~icon_data->dwStateMask) | (icon_data->dwState & icon_data->dwStateMask); + if (notifications_dst_path && notifications_dst_path[0] && + !(icon->state & NIS_HIDDEN) && (icon_data->uFlags & NIF_INFO) && icon_data->cbSize >= NOTIFYICONDATAA_V2_SIZE) + send_notification(icon->connection, + icon->notification_id, + icon_data->szInfoTitle, + icon_data->szInfo, + icon_data->hBalloonIcon, + icon_data->dwInfoFlags, + icon_data->uTimeout, + &icon->notification_id); + icon->version = icon_data->uVersion; + if (!p_dbus_connection_try_register_object_path(connection, object_path, ¬ification_vtable, icon, &error)) + { + WARN("failed register object %s: %s\n", error.name, error.message); + p_dbus_error_free( &error ); + goto fail; + } + registered = true; + /* don't register, if there is no SNWatcher available, it might be reinitializing */ + if (status_notifier_dst_path != NULL && status_notifier_dst_path[0] != '\0' && + !register_notification_item(connection)) + { + WARN("failed to register item\n"); + p_dbus_connection_unregister_object_path(connection, object_path); + goto fail; + } + list_add_tail(&sni_list, &icon->entry); + return TRUE; +fail: + if (icon && icon->hIcon) NtUserDestroyCursor(icon->hIcon, 0); + if (icon && icon->icon_bitmap) free(icon->icon_bitmap); + if (icon) pthread_mutex_destroy(&icon->mutex); + free(icon); + if (registered) + p_dbus_connection_unregister_object_path(connection, object_path); + if (connection) + p_dbus_connection_close(connection); + return FALSE; +} + +BOOL cleanup_icon(struct tray_icon* icon) +{ + pthread_mutex_lock(&icon->mutex); + p_dbus_connection_flush(icon->connection); + p_dbus_connection_close(icon->connection); + p_dbus_connection_unref(icon->connection); + pthread_mutex_unlock(&icon->mutex); + + if (icon->hIcon) NtUserDestroyCursor(icon->hIcon, 0); + if (icon->icon_bitmap) free(icon->icon_bitmap); + if (icon) pthread_mutex_destroy(&icon->mutex); + + free(icon); + return TRUE; +} + +BOOL snidrv_delete_notify_icon( HWND hwnd, UINT uID ) +{ + struct tray_icon *icon = NULL, *this, *next; + pthread_mutex_lock(&list_mutex); + LIST_FOR_EACH_ENTRY_SAFE( this, next, &sni_list, struct tray_icon, entry ) + { + if ((this->id == uID) && (this->owner == hwnd)) + { + list_remove(&this->entry); + icon = this; + break; + } + } + pthread_mutex_unlock(&list_mutex); + if (!icon) return FALSE; + return cleanup_icon(icon); +} + +BOOL snidrv_modify_notify_icon( const NOTIFYICONDATAW* icon_data ) +{ + struct tray_icon* icon; + const char* pending_signals[4]; + UINT signal_count = 0; + UINT new_state; + UINT i; + icon = get_icon(icon_data->hWnd, icon_data->uID); + if (!icon) + return FALSE; + + pthread_mutex_lock(&icon->mutex); + + if (icon_data->uFlags & NIF_ICON) + { + if (!get_icon_data(icon_data, icon)) + goto err; + pending_signals[signal_count++] = "NewIcon"; + } + + if (icon_data->uFlags & NIF_MESSAGE) + { + icon->callback_message = icon_data->uCallbackMessage; + } + + if (icon_data->uFlags & NIF_STATE) + { + new_state = (icon->state & ~icon_data->dwStateMask) | (icon_data->dwState & icon_data->dwStateMask); + if (new_state != icon->state) + { + icon->state = new_state; + pending_signals[signal_count++] = "NewStatus"; + } + } + + if (icon_data->uFlags & NIF_TIP) + { + ntdll_wcstoumbs(icon_data->szTip, wcslen(icon_data->szTip) + 1, icon->tiptext, ARRAY_SIZE(icon->tiptext), FALSE); + pending_signals[signal_count++] = "NewTitle"; + } + + pthread_mutex_unlock(&icon->mutex); + + /* send the signals */ + for (i = 0; i < signal_count; i++) + { + if (!send_signal_to_item(icon->connection, pending_signals[i])) + goto err_post_unlock; + } + + if (notifications_dst_path && notifications_dst_path[0]) + { + if (!(icon->state & NIS_HIDDEN) && (icon_data->uFlags & NIF_INFO) && icon_data->cbSize >= NOTIFYICONDATAA_V2_SIZE) + send_notification(icon->connection, icon->notification_id, icon_data->szInfoTitle, icon_data->szInfo, icon_data->hBalloonIcon, icon_data->dwInfoFlags, icon_data->uTimeout, &icon->notification_id); + else if ((icon->state & NIS_HIDDEN) && icon->notification_id) + close_notification(icon->connection, icon->notification_id); + } + return TRUE; +err: + pthread_mutex_unlock(&icon->mutex); +err_post_unlock: + return FALSE; +} + +BOOL snidrv_set_notify_icon_version( HWND hwnd, UINT uID, UINT uVersion) +{ + struct tray_icon* icon; + icon = get_icon(hwnd, uID); + if (!icon) + return FALSE; + pthread_mutex_lock(&icon->mutex); + icon->version = uVersion; + pthread_mutex_unlock(&icon->mutex); + + return TRUE; +} + +BOOL snidrv_cleanup_notify_icons(HWND owner) +{ + struct tray_icon *this, *next; + + pthread_mutex_lock(&list_mutex); + LIST_FOR_EACH_ENTRY_SAFE( this, next, &sni_list, struct tray_icon, entry ) + { + if (this->owner == owner) + { + list_remove(&this->entry); + cleanup_icon(this); + } + } + pthread_mutex_unlock(&list_mutex); + return TRUE; +} + +BOOL snidrv_show_balloon( HWND owner, UINT id, BOOL hidden, const struct systray_balloon* balloon ) +{ + BOOL ret = TRUE; + struct standalone_notification *found_notification = NULL, *this; + + if (!notifications_dst_path || !notifications_dst_path[0]) + return -1; + pthread_mutex_lock(&standalone_notifications_mutex); + + LIST_FOR_EACH_ENTRY(this, &standalone_notification_list, struct standalone_notification, entry) + { + if (this->owner == owner && this->id == id) + { + found_notification = this; + break; + } + } + /* close existing notification anyway */ + if (!hidden) + { + if (!found_notification) + { + found_notification = malloc(sizeof(struct standalone_notification)); + if (!found_notification) + { + ret = FALSE; + goto cleanup; + } + found_notification->owner = owner; + found_notification->id = id; + found_notification->notification_id = 0; + list_add_tail(&standalone_notification_list, &found_notification->entry); + } + else + TRACE("found existing notification %p %d\n", owner, id); + ret = send_notification(global_connection, + found_notification->notification_id, + balloon->info_title, + balloon->info_text, + balloon->info_icon, + balloon->info_flags, + balloon->info_timeout, + &found_notification->notification_id); + } + else if (found_notification) + { + ret = close_notification(global_connection, found_notification->notification_id); + } +cleanup: + pthread_mutex_unlock(&standalone_notifications_mutex); + return ret; +} diff --git a/dlls/win32u/snidrv/image.c b/dlls/win32u/snidrv/image.c new file mode 100644 index 00000000000..3343c6cbd39 --- /dev/null +++ b/dlls/win32u/snidrv/image.c @@ -0,0 +1,167 @@ +/* + * DBus tray support + * + * Copyright 2023 Sergei Chernyadyev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include "snidrv.h" +#include "stdlib.h" +#include "ntuser.h" +#include "ntgdi.h" + +/*********************************************************************** + * 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 (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return NULL; + stride = ((bm.bmWidth + 15) >> 3) & ~1; + mask_size = stride * bm.bmHeight; + if (!(mask = malloc( mask_size ))) return NULL; + if (!NtGdiGetBitmapBits( bmp, mask_size, mask )) goto done; + + bm.bmHeight /= 2; + bits_size = bm.bmWidth * bm.bmHeight * sizeof(*bits); + if (!(bits = malloc( 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 */ +#ifdef WORDS_BIGENDIAN + *ptr = 0xff000000; +#else + *ptr = 0x000000ff; +#endif + } + + *width = bm.bmWidth; + *height = bm.bmHeight; + +done: + free( 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 (!NtGdiExtGetObjectW( 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 = malloc( bm.bmWidth * bm.bmHeight * sizeof(unsigned int) ))) + goto failed; + if (!NtGdiGetDIBitsInternal( hdc, color, 0, bm.bmHeight, bits, info, DIB_RGB_COLORS, 0, 0 )) + 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 = malloc( info->bmiHeader.biSizeImage ))) goto failed; + if (!NtGdiGetDIBitsInternal( hdc, mask, 0, bm.bmHeight, mask_bits, info, DIB_RGB_COLORS, 0, 0 )) + 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; + free( mask_bits ); + } +#ifndef WORDS_BIGENDIAN + for (unsigned i = 0; i < bm.bmWidth * bm.bmHeight; i++) + { + bits[i] = ((bits[i] & 0xFF) << 24) | ((bits[i] & 0xFF00) << 8) | ((bits[i] & 0xFF0000) >> 8) | ((bits[i] & 0xFF000000) >> 24); + } +#endif + + return bits; + +failed: + free( bits ); + free( mask_bits ); + *width = *height = 0; + return NULL; +} + + +BOOL create_bitmap_from_icon(HANDLE icon, unsigned *p_width, unsigned *p_height, void** p_bits) +{ + ICONINFO info; + HDC hdc; + + if (!NtUserGetIconInfo(icon, &info, NULL, NULL, NULL, 0)) + return FALSE; + + hdc = NtGdiCreateCompatibleDC( 0 ); + *p_bits = get_bitmap_argb( hdc, info.hbmColor, info.hbmMask, p_width, p_height ); + NtGdiDeleteObjectApp( info.hbmMask ); + NtGdiDeleteObjectApp( hdc ); + return *p_bits != NULL; +} + diff --git a/dlls/win32u/snidrv/snidrv.h b/dlls/win32u/snidrv/snidrv.h new file mode 100644 index 00000000000..d0f137b24a7 --- /dev/null +++ b/dlls/win32u/snidrv/snidrv.h @@ -0,0 +1,45 @@ +/* + * SNI driver include file + * + * Copyright 2023 Sergei Chernyadyev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINE_SNIDRV_H +#define __WINE_SNIDRV_H + +#include <stdarg.h> +#include "windef.h" +#include "winbase.h" +#include "winuser.h" +#include "shellapi.h" +#include "ntuser.h" + +/* snidrv */ +extern BOOL snidrv_init(void); +extern BOOL snidrv_notification_init(void); +extern BOOL snidrv_run_loop(void); + +extern BOOL snidrv_add_notify_icon( const NOTIFYICONDATAW* icon_data); +extern BOOL snidrv_modify_notify_icon( const NOTIFYICONDATAW* icon_data); +extern BOOL snidrv_delete_notify_icon( HWND hwnd, UINT uID ); +extern BOOL snidrv_set_notify_icon_version( HWND hwnd, UINT uID, UINT uVersion); + +extern BOOL create_bitmap_from_icon(HANDLE icon, unsigned *p_width, unsigned *p_height, void** p_bits); +extern BOOL snidrv_cleanup_notify_icons(HWND owner); + +extern BOOL snidrv_show_balloon( HWND owner, UINT id, BOOL hidden, const struct systray_balloon* balloon ); +#endif diff --git a/dlls/win32u/systray.c b/dlls/win32u/systray.c index 1df2b97da9a..c3b9fbefc8a 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -21,18 +21,65 @@ #endif
#include "config.h" - #include "ntstatus.h" #define WIN32_NO_STATUS #include "win32u_private.h" #include "ntuser_private.h" +#include "snidrv/snidrv.h" #include "shellapi.h" #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(systray);
+#ifdef SONAME_LIBDBUS_1 +static volatile LONG sni_initialized = (LONG)FALSE; +static volatile LONG dbus_notifications_initialized = (LONG)FALSE; +#endif + LRESULT system_tray_call( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, void *data ) { +#ifdef SONAME_LIBDBUS_1 + LONG l_sni_initialized = InterlockedCompareExchange(&sni_initialized, (LONG)FALSE, (LONG)FALSE); + if (!l_sni_initialized && snidrv_init()) + { + InterlockedCompareExchange(&sni_initialized, TRUE, FALSE); + l_sni_initialized = TRUE; + } + if (l_sni_initialized) + { + // SNI + switch (msg) + { + case WINE_SYSTRAY_ADD_NOTIFY_ICON: + return snidrv_add_notify_icon( data ); + case WINE_SYSTRAY_MODIFY_NOTIFY_ICON: + return snidrv_modify_notify_icon( data ); + case WINE_SYSTRAY_DELETE_NOTIFY_ICON: + return snidrv_delete_notify_icon( hwnd, wparam ); + case WINE_SYSTRAY_SET_NOTIFY_ICON_VERSION: + return snidrv_set_notify_icon_version( hwnd, wparam, lparam ); + case WINE_SYSTRAY_CLEANUP_ICONS: + return snidrv_cleanup_notify_icons( hwnd ); + case WINE_SYSTRAY_RUN_LOOP: + return snidrv_run_loop(); + default: + break; + } + } + + if (msg == WINE_SYSTRAY_SHOW_BALLOON) + { + LONG l_dbus_notifications_initialized = InterlockedCompareExchange(&dbus_notifications_initialized, (LONG)FALSE, (LONG)FALSE); + if (!l_dbus_notifications_initialized && snidrv_notification_init()) + { + InterlockedCompareExchange(&dbus_notifications_initialized, TRUE, FALSE); + l_dbus_notifications_initialized = TRUE; + } + if (l_dbus_notifications_initialized) + return snidrv_show_balloon(hwnd, wparam, lparam, data); + } +#endif + switch (msg) { case WINE_SYSTRAY_ADD_NOTIFY_ICON: