Adds the notification balloons implementation based on org.freedesktop.Notifications interface usage, if it's available. This is a split version of https://gitlab.winehq.org/wine/wine/-/merge_requests/2808 , because currently there is no documented way to maintain a single connection for a whole system tray with multiple StatusNotifierItems.
From: Sergei Chernyadyev serg.cherniadjev@gmail.com
--- 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 67217dad634..b5fc44acdb4 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -52,6 +52,8 @@ 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_RUN_LOOP: + return -1; default: FIXME( "Unknown NtUserSystemTrayCall msg %#x\n", msg ); break; diff --git a/include/ntuser.h b/include/ntuser.h index 4148c8cf0b6..4cbd3bf11ab 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -547,6 +547,7 @@ enum wine_systray_call WINE_SYSTRAY_DOCK_INSERT, WINE_SYSTRAY_DOCK_CLEAR, WINE_SYSTRAY_DOCK_REMOVE, + WINE_SYSTRAY_RUN_LOOP, };
#define WM_SYSTIMER 0x0118 diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 275c683e3ff..aa3203bc46c 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -1146,6 +1146,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, BOOL arg_show_systray ) { @@ -1190,6 +1195,8 @@ void initialize_systray( BOOL using_root, BOOL arg_enable_shell, BOOL arg_show_s 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 serg.cherniadjev@gmail.com
--- dlls/win32u/Makefile.in | 5 +- dlls/win32u/snidrv/dbus.c | 240 ++++++++++++++++++++++++++++++++++++ dlls/win32u/snidrv/snidrv.h | 35 ++++++ 3 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 dlls/win32u/snidrv/dbus.c create mode 100644 dlls/win32u/snidrv/snidrv.h
diff --git a/dlls/win32u/Makefile.in b/dlls/win32u/Makefile.in index 6326a3cd302..233d2c5d169 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
@@ -48,6 +48,7 @@ SOURCES = \ rawinput.c \ region.c \ scroll.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..48a218d2968 --- /dev/null +++ b/dlls/win32u/snidrv/dbus.c @@ -0,0 +1,240 @@ +/* + * 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" + +#ifdef SONAME_LIBDBUS_1 +#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_get_private); \ + DO_FUNC(dbus_connection_dispatch); \ + DO_FUNC(dbus_connection_get_dispatch_status); \ + DO_FUNC(dbus_connection_flush); \ + DO_FUNC(dbus_connection_close); \ + DO_FUNC(dbus_connection_ref); \ + DO_FUNC(dbus_connection_unref); \ + 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_threads_init_default) + +#define DO_FUNC(f) static typeof(f) * p_##f +DBUS_FUNCS; +#undef DO_FUNC + +static pthread_once_t init_control = PTHREAD_ONCE_INIT; + +static void* dbus_module = NULL; + +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 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 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 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; + /* TODO: replace this with Interlocked if there will be a getter function for this variable */ + sni_initialized = TRUE; +err: + if (!sni_initialized) + { + dbus_finalize(); + } +} + +BOOL snidrv_init(void) +{ + pthread_once(&init_control, snidrv_once_initialize); + return sni_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; + + /* global connection */ + global_connection_watch_fd = fd; + global_connection_watch_flags = poll_flags; + global_connection_watch = w; + + return TRUE; +} + +static void remove_watch(DBusWatch *w, void *data) +{ + /* global connection */ + global_connection_watch_fd = 0; + global_connection_watch_flags = 0; + global_connection_watch = NULL; +} + + +static void toggle_watch(DBusWatch *w, void *data) +{ + if (p_dbus_watch_get_enabled(w)) + add_watch(w, data); + else + remove_watch(w, data); +} + +BOOL snidrv_run_loop() +{ + while (true) + { + int poll_ret; + struct pollfd fd_info; + DBusConnection* conn; + /* TODO: add condvar */ + if (!global_connection_watch_fd) continue; + + conn = p_dbus_connection_ref(global_connection); + fd_info = (struct pollfd) { + .fd = global_connection_watch_fd, + .events = global_connection_watch_flags, + .revents = 0, + }; + + poll_ret = poll(&fd_info, 1, 100); + if (poll_ret == 0) + goto cleanup; + if (poll_ret == -1) + { + ERR("fd poll error\n"); + goto cleanup; + } + + if (fd_info.revents & (POLLERR | POLLHUP | POLLNVAL)) continue; + if (fd_info.revents & POLLIN) + { + p_dbus_watch_handle(global_connection_watch, DBUS_WATCH_READABLE); + while ( p_dbus_connection_get_dispatch_status ( conn ) == DBUS_DISPATCH_DATA_REMAINS ) + { + p_dbus_connection_dispatch ( conn ) ; + } + } + + if (fd_info.revents & POLLOUT) + p_dbus_watch_handle(global_connection_watch, DBUS_WATCH_WRITABLE); + cleanup: + p_dbus_connection_unref(conn); + } + + return 0; +} + +#endif diff --git a/dlls/win32u/snidrv/snidrv.h b/dlls/win32u/snidrv/snidrv.h new file mode 100644 index 00000000000..75aefcc67fc --- /dev/null +++ b/dlls/win32u/snidrv/snidrv.h @@ -0,0 +1,35 @@ +/* + * 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_run_loop(void); + +#endif
From: Sergei Chernyadyev serg.cherniadjev@gmail.com
--- dlls/win32u/driver.c | 12 ++++++++++++ dlls/win32u/systray.c | 4 ++++ dlls/wow64win/user.c | 19 +++++++++++++++++++ include/ntuser.h | 10 ++++++++++ include/wine/gdi_driver.h | 1 + programs/explorer/systray.c | 13 ++++++++++++- 6 files changed, 58 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/driver.c b/dlls/win32u/driver.c index 76e717648d6..7670dc83bf2 100644 --- a/dlls/win32u/driver.c +++ b/dlls/win32u/driver.c @@ -741,6 +741,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) { } @@ -1166,6 +1171,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; @@ -1264,6 +1274,7 @@ static const struct user_driver_funcs lazy_load_driver = loaderdrv_SystrayDockInsert, loaderdrv_SystrayDockClear, loaderdrv_SystrayDockRemove, + loaderdrv_SystrayShowBalloon, /* clipboard functions */ nulldrv_ClipboardWindowProc, loaderdrv_UpdateClipboard, @@ -1355,6 +1366,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 b5fc44acdb4..12276f70497 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -54,6 +54,10 @@ LRESULT system_tray_call( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, voi
case WINE_SYSTRAY_RUN_LOOP: return -1; + + 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 db0fed3d97d..d3beddce99e 100644 --- a/dlls/wow64win/user.c +++ b/dlls/wow64win/user.c @@ -3659,6 +3659,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 4cbd3bf11ab..d187df65f25 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -548,6 +548,16 @@ enum wine_systray_call WINE_SYSTRAY_DOCK_CLEAR, WINE_SYSTRAY_DOCK_REMOVE, WINE_SYSTRAY_RUN_LOOP, + 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 774211fea15..a03651d835f 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 aa3203bc46c..fcf6694725f 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -283,7 +283,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 serg.cherniadjev@gmail.com
--- dlls/win32u/Makefile.in | 1 + dlls/win32u/snidrv/dbus.c | 529 +++++++++++++++++++++++++++++++++++- dlls/win32u/snidrv/image.c | 169 ++++++++++++ dlls/win32u/snidrv/snidrv.h | 7 +- 4 files changed, 698 insertions(+), 8 deletions(-) create mode 100644 dlls/win32u/snidrv/image.c
diff --git a/dlls/win32u/Makefile.in b/dlls/win32u/Makefile.in index 233d2c5d169..acb3f6c4da9 100644 --- a/dlls/win32u/Makefile.in +++ b/dlls/win32u/Makefile.in @@ -48,6 +48,7 @@ SOURCES = \ rawinput.c \ region.c \ scroll.c \ + snidrv/image.c \ snidrv/dbus.c \ spy.c \ syscall.c \ diff --git a/dlls/win32u/snidrv/dbus.c b/dlls/win32u/snidrv/dbus.c index 48a218d2968..8b62cee4bbf 100644 --- a/dlls/win32u/snidrv/dbus.c +++ b/dlls/win32u/snidrv/dbus.c @@ -47,13 +47,29 @@ 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_set_watch_functions); \ DO_FUNC(dbus_watch_get_unix_fd); \ DO_FUNC(dbus_watch_handle); \ @@ -62,7 +78,36 @@ WINE_DEFAULT_DEBUG_CHANNEL(winesni); DO_FUNC(dbus_error_free); \ DO_FUNC(dbus_error_init); \ DO_FUNC(dbus_error_is_set); \ - DO_FUNC(dbus_threads_init_default) + 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; @@ -70,6 +115,24 @@ DBUS_FUNCS;
static pthread_once_t init_control = PTHREAD_ONCE_INIT;
+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 const char* notifications_interface_name = "org.freedesktop.Notifications"; + static void* dbus_module = NULL;
static DBusConnection *global_connection; @@ -77,6 +140,9 @@ 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* notifications_dst_path = NULL;
static BOOL load_dbus_functions(void) { @@ -93,6 +159,11 @@ failed: return FALSE; }
+static void notifications_finalize(void) +{ + free(notifications_dst_path); +} + static void dbus_finalize(void) { if (global_connection != NULL) @@ -111,6 +182,8 @@ 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; @@ -136,19 +209,22 @@ static void snidrv_once_initialize(void) { if (!load_dbus_functions()) goto err; if (!dbus_initialize()) goto err; - /* TODO: replace this with Interlocked if there will be a getter function for this variable */ + + if (notifications_initialize()) + notifications_initialized = TRUE; + sni_initialized = TRUE; err: - if (!sni_initialized) - { + if (!notifications_initialized) + notifications_finalize(); + if (!sni_initialized && !notifications_initialized) dbus_finalize(); - } }
-BOOL snidrv_init(void) +BOOL snidrv_notification_init(void) { pthread_once(&init_control, snidrv_once_initialize); - return sni_initialized; + return notifications_initialized; }
static dbus_bool_t add_watch(DBusWatch *w, void *data) @@ -192,6 +268,391 @@ static void toggle_watch(DBusWatch *w, void *data) 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 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 )) + { + 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 notifications_initialize(void) +{ + DBusError error; + p_dbus_error_init( &error ); + + if (!get_owner_for_interface(global_connection, "org.freedesktop.Notifications", ¬ifications_dst_path)) + { + 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; + } + + return TRUE; +err: + return FALSE; +} + +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; +} + BOOL snidrv_run_loop() { while (true) @@ -237,4 +698,58 @@ BOOL snidrv_run_loop() return 0; }
+ +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; +} #endif diff --git a/dlls/win32u/snidrv/image.c b/dlls/win32u/snidrv/image.c new file mode 100644 index 00000000000..122107b4f5d --- /dev/null +++ b/dlls/win32u/snidrv/image.c @@ -0,0 +1,169 @@ +/* + * 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" +#ifdef SONAME_LIBDBUS_1 + +#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; +} + +#endif diff --git a/dlls/win32u/snidrv/snidrv.h b/dlls/win32u/snidrv/snidrv.h index 75aefcc67fc..8b37899c179 100644 --- a/dlls/win32u/snidrv/snidrv.h +++ b/dlls/win32u/snidrv/snidrv.h @@ -29,7 +29,12 @@ #include "ntuser.h"
/* snidrv */ -extern BOOL snidrv_init(void); +extern BOOL snidrv_notification_init(void); extern BOOL snidrv_run_loop(void);
+ +extern BOOL create_bitmap_from_icon(HANDLE icon, unsigned *p_width, unsigned *p_height, void** p_bits); + +extern BOOL snidrv_show_balloon( HWND owner, UINT id, BOOL hidden, const struct systray_balloon* balloon ); + #endif
From: Sergei Chernyadyev serg.cherniadjev@gmail.com
--- dlls/win32u/systray.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
diff --git a/dlls/win32u/systray.c b/dlls/win32u/systray.c index 12276f70497..d00afeced65 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -26,13 +26,37 @@ #define WIN32_NO_STATUS #include "win32u_private.h" #include "ntuser_private.h" +#ifdef SONAME_LIBDBUS_1 +#include "snidrv/snidrv.h" +#endif #include "shellapi.h" #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(systray);
+#ifdef SONAME_LIBDBUS_1 +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_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) + { + if (msg == WINE_SYSTRAY_RUN_LOOP) + return snidrv_run_loop(); + if (msg == WINE_SYSTRAY_SHOW_BALLOON) + return snidrv_show_balloon(hwnd, wparam, lparam, data); + } + +#endif + switch (msg) { case WINE_SYSTRAY_NOTIFY_ICON:
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=147535
Your paranoid android.
=== debian11b (64 bit WoW report) ===
user32: input.c:4305: Test succeeded inside todo block: button_down_hwnd_todo 1: got MSG_TEST_WIN hwnd 0000000000B400D6, msg WM_LBUTTONDOWN, wparam 0x1, lparam 0x320032 input.c:733: Test failed: peek: raw_legacy: 0: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x1, flags 0, vkey F, message WM_KEYDOWN, extra 0 input.c:733: Test failed: peek: raw_legacy: 0: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYDOWN, wparam 0x46, lparam 0x10001 input.c:733: Test failed: peek: raw_legacy: 0: test->expect 2 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_CHAR, wparam 0x66, lparam 0x10001 input.c:734: Test failed: peek: raw_legacy: 0: got F: 0 input.c:733: Test failed: peek: raw_legacy: 1: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x2, flags 0x1, vkey F, message WM_KEYUP, extra 0 input.c:733: Test failed: peek: raw_legacy: 1: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYUP, wparam 0x46, lparam 0xffffffffc0020001 input.c:733: Test failed: peek: raw_vk_packet_legacy: 0: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x1, flags 0, vkey 0xe7, message WM_KEYDOWN, extra 0 input.c:733: Test failed: peek: raw_vk_packet_legacy: 0: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYDOWN, wparam 0xe7, lparam 0x10001 input.c:734: Test failed: peek: raw_vk_packet_legacy: 0: got 0xe7: 0 input.c:733: Test failed: peek: raw_vk_packet_legacy: 1: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x2, flags 0x1, vkey 0xe7, message WM_KEYUP, extra 0 input.c:733: Test failed: peek: raw_vk_packet_legacy: 1: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYUP, wparam 0xe7, lparam 0xffffffffc0020001 input.c:733: Test failed: peek: raw_unicode_legacy: 0: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_CHAR, wparam 0x3c0, lparam 0x1 input.c:734: Test failed: peek: raw_unicode_legacy: 0: got 0xe7: 0 input.c:733: Test failed: peek: raw_unicode_vkey_ctrl_legacy: 0: test->expect 0 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYDOWN, wparam 0x11, lparam 0xc00001 input.c:734: Test failed: peek: raw_unicode_vkey_ctrl_legacy: 0: got VK_CONTROL: 0 input.c:734: Test failed: peek: raw_unicode_vkey_ctrl_legacy: 0: got VK_LCONTROL: 0 input.c:733: Test failed: peek: raw_unicode_vkey_ctrl_legacy: 1: test->expect 0 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYUP, wparam 0x11, lparam 0xffffffffc0c00001 input.c:733: Test failed: peek: raw_nolegacy: 0: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x1, flags 0, vkey F, message WM_KEYDOWN, extra 0 input.c:733: Test failed: peek: raw_nolegacy: 1: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x2, flags 0x1, vkey F, message WM_KEYUP, extra 0 input.c:733: Test failed: peek: raw_vk_packet_nolegacy: 0: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x1, flags 0, vkey 0xe7, message WM_KEYDOWN, extra 0 input.c:733: Test failed: peek: raw_vk_packet_nolegacy: 1: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x2, flags 0x1, vkey 0xe7, message WM_KEYUP, extra 0 input.c:733: Test failed: receive: raw_legacy: 0: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x1, flags 0, vkey F, message WM_KEYDOWN, extra 0 input.c:733: Test failed: receive: raw_legacy: 0: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYDOWN, wparam 0x46, lparam 0x10001 input.c:733: Test failed: receive: raw_legacy: 0: test->expect 2 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_CHAR, wparam 0x66, lparam 0x10001 input.c:734: Test failed: receive: raw_legacy: 0: got F: 0 input.c:733: Test failed: receive: raw_legacy: 1: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x2, flags 0x1, vkey F, message WM_KEYUP, extra 0 input.c:733: Test failed: receive: raw_legacy: 1: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYUP, wparam 0x46, lparam 0xffffffffc0020001 input.c:733: Test failed: receive: raw_vk_packet_legacy: 0: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x1, flags 0, vkey 0xe7, message WM_KEYDOWN, extra 0 input.c:733: Test failed: receive: raw_vk_packet_legacy: 0: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYDOWN, wparam 0xe7, lparam 0x10001 input.c:734: Test failed: receive: raw_vk_packet_legacy: 0: got 0xe7: 0 input.c:733: Test failed: receive: raw_vk_packet_legacy: 1: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x2, flags 0x1, vkey 0xe7, message WM_KEYUP, extra 0 input.c:733: Test failed: receive: raw_vk_packet_legacy: 1: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYUP, wparam 0xe7, lparam 0xffffffffc0020001 input.c:733: Test failed: receive: raw_unicode_legacy: 0: test->expect 1 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_CHAR, wparam 0x3c0, lparam 0x1 input.c:734: Test failed: receive: raw_unicode_legacy: 0: got 0xe7: 0 input.c:733: Test failed: receive: raw_unicode_vkey_ctrl_legacy: 0: test->expect 0 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYDOWN, wparam 0x11, lparam 0xc00001 input.c:734: Test failed: receive: raw_unicode_vkey_ctrl_legacy: 0: got VK_CONTROL: 0 input.c:734: Test failed: receive: raw_unicode_vkey_ctrl_legacy: 0: got VK_LCONTROL: 0 input.c:733: Test failed: receive: raw_unicode_vkey_ctrl_legacy: 1: test->expect 0 (missing): MSG_TEST_WIN hwnd 0000000000000000, WM_KEYUP, wparam 0x11, lparam 0xffffffffc0c00001 input.c:733: Test failed: receive: raw_nolegacy: 0: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x1, flags 0, vkey F, message WM_KEYDOWN, extra 0 input.c:733: Test failed: receive: raw_nolegacy: 1: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x2, flags 0x1, vkey F, message WM_KEYUP, extra 0 input.c:733: Test failed: receive: raw_vk_packet_nolegacy: 0: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x1, flags 0, vkey 0xe7, message WM_KEYDOWN, extra 0 input.c:733: Test failed: receive: raw_vk_packet_nolegacy: 1: test->expect 0 (missing): got WM_INPUT key hwnd 0000000000000000, code 0, make_code 0x2, flags 0x1, vkey 0xe7, message WM_KEYUP, extra 0
Instead of open-coding HICON-to-bitmap logic, I believe we should do the conversion *inside* explorer via WIC (windowscodecs), and pass the converted ARGB bitmap to unix side.