From b0c6e9fd54fa7e9ae405a94d8ec8afaffe5b44fb Mon Sep 17 00:00:00 2001 From: Sergei Chernyadyev Date: Mon, 4 Dec 2023 17:40:28 +0300 Subject: [PATCH 1/5] win32u: Add a SystrayRunLoop driver interface --- 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 f50b59c615536e3d1ede87f8d39ed56c8f5a1277 Mon Sep 17 00:00:00 2001 From: Sergei Chernyadyev Date: Wed, 6 Dec 2023 09:44:41 +0300 Subject: [PATCH 2/5] win32u: Add DBus event loop for SNI handling --- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 +#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 63bd3ffa9bad8c8bafb1302a3f19e304b51ab76c Mon Sep 17 00:00:00 2001 From: Sergei Chernyadyev Date: Mon, 4 Dec 2023 17:40:09 +0300 Subject: [PATCH 3/5] win32u: Add a ShowBalloon driver interface --- 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 bfbfbf0aeaabbb16a460d666ed0facf5eb1ceb6e Mon Sep 17 00:00:00 2001 From: Sergei Chernyadyev Date: Sat, 3 Aug 2024 20:27:19 +0300 Subject: [PATCH 4/5] win32u: Handle notification balloons through org.freedesktop.Notifications dbus interface --- 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 8f928ad024cedeafebe73a03a6e8d6ce940453e1 Mon Sep 17 00:00:00 2001 From: Sergei Chernyadyev Date: Sat, 3 Aug 2024 20:36:11 +0300 Subject: [PATCH 5/5] win32u: Handle snidrv initialization and loop management from system_tray_call --- 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: