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.
-- v41: win32u: Handle dbus notification balloons from system_tray_call win32u: Handle notification balloons through org.freedesktop.Notifications dbus interface win32u: Add a ShowBalloon driver interface win32u: Handle StatusNotifierItem management from system_tray_call win32u: Handle StatusNotifierWatcher owner changing and registering objects to a new watcher win32u: Add SNI driver for systray handling win32u: Add DBus event loop for SNI handling win32u: Add a SystrayRunLoop driver interface
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 dd19c57221e..f794a3ba752 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -530,6 +530,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 1af8a72de46..428df70755e 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -1104,6 +1104,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 ) { @@ -1147,6 +1152,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 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 878baeaffbe..ebf5481a50a 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,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/Makefile.in | 1 + dlls/win32u/snidrv/dbus.c | 1069 +++++++++++++++++++++++++++++++++-- dlls/win32u/snidrv/image.c | 169 ++++++ dlls/win32u/snidrv/snidrv.h | 7 + 4 files changed, 1211 insertions(+), 35 deletions(-) create mode 100644 dlls/win32u/snidrv/image.c
diff --git a/dlls/win32u/Makefile.in b/dlls/win32u/Makefile.in index ebf5481a50a..acf9f73fa89 100644 --- a/dlls/win32u/Makefile.in +++ b/dlls/win32u/Makefile.in @@ -47,6 +47,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..5ffc427f26a 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,15 +78,71 @@ 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; #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 */ + 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; + static void* dbus_module = NULL; +static const char* watcher_interface_name = "org.kde.StatusNotifierWatcher"; +static const char* item_interface_name = "org.kde.StatusNotifierItem";
static DBusConnection *global_connection; static DBusWatch *global_connection_watch; @@ -78,6 +150,17 @@ static int global_connection_watch_fd; static UINT global_connection_watch_flags; static BOOL sni_initialized = FALSE;
+static char* status_notifier_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 ))) @@ -93,6 +176,12 @@ failed: return FALSE; }
+static void sni_finalize(void) +{ + pthread_mutex_destroy(&list_mutex); + free(status_notifier_dst_path); +} + static void dbus_finalize(void) { if (global_connection != NULL) @@ -136,11 +225,13 @@ 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; + if (get_notifier_watcher_owner()) + /* TODO: replace this with Interlocked if there will be a getter function for this variable */ + sni_initialized = TRUE; err: if (!sni_initialized) { + sni_finalize(); dbus_finalize(); } } @@ -167,23 +258,46 @@ static dbus_bool_t add_watch(DBusWatch *w, void *data) 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; + 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) { - /* global connection */ - global_connection_watch_fd = 0; - global_connection_watch_flags = 0; - global_connection_watch = NULL; + 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)) @@ -192,24 +306,595 @@ static void toggle_watch(DBusWatch *w, void *data) remove_watch(w, data); }
+static const char* const object_path = "/StatusNotifierItem"; + +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, watcher_interface_name, item_interface_name)) + { + WARN("failed to query watcher interface owner\n"); + goto err; + } + + 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 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, 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(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, 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, 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, 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, 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 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, - }; + int i, poll_ret; + struct tray_icon* icon; + fd_count = 0; + /* TODO: add condvar if there are no connections available */ + 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_info, 1, 100); + poll_ret = poll(fd_ptr, fd_count, 100); if (poll_ret == 0) goto cleanup; if (poll_ret == -1) @@ -218,23 +903,337 @@ BOOL snidrv_run_loop() goto cleanup; }
- if (fd_info.revents & (POLLERR | POLLHUP | POLLNVAL)) continue; - if (fd_info.revents & POLLIN) + for ( i = 0; i < fd_count; i++ ) { - p_dbus_watch_handle(global_connection_watch, DBUS_WATCH_READABLE); - while ( p_dbus_connection_get_dispatch_status ( conn ) == DBUS_DISPATCH_DATA_REMAINS ) + if (fd_info[i].revents & (POLLERR | POLLHUP | POLLNVAL)) continue; + if (fd_info[i].revents & POLLIN) { - p_dbus_connection_dispatch ( conn ) ; + 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); } - - if (fd_info.revents & POLLOUT) - p_dbus_watch_handle(global_connection_watch, DBUS_WATCH_WRITABLE); cleanup: - p_dbus_connection_unref(conn); + 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* connection_service_name = p_dbus_bus_get_unique_name(connection); + + 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, &connection_service_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); + + 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; +} + +static 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; + } + + 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; +} + #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..5fd1dcfc279 100644 --- a/dlls/win32u/snidrv/snidrv.h +++ b/dlls/win32u/snidrv/snidrv.h @@ -32,4 +32,11 @@ extern BOOL snidrv_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); #endif
From: Sergei Chernyadyev serg.cherniadjev@gmail.com
--- dlls/win32u/snidrv/dbus.c | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+)
diff --git a/dlls/win32u/snidrv/dbus.c b/dlls/win32u/snidrv/dbus.c index 5ffc427f26a..03f13c322a5 100644 --- a/dlls/win32u/snidrv/dbus.c +++ b/dlls/win32u/snidrv/dbus.c @@ -306,8 +306,69 @@ 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* 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 )) + { + /* check if watcher is disabled first*/ + if (strcmp(interface_name, watcher_interface_name) == 0) + { + /* 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 */ + + 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); + } + } + } + + 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; @@ -382,6 +443,8 @@ static BOOL get_notifier_watcher_owner(void) 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 ); if (p_dbus_error_is_set(&error)) { WARN("failed to register matcher %s: %s\n", error.name, error.message);
From: Sergei Chernyadyev serg.cherniadjev@gmail.com
--- dlls/win32u/systray.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/systray.c b/dlls/win32u/systray.c index b5fc44acdb4..003bceff122 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -21,18 +21,57 @@ #endif
#include "config.h" - #include "ntstatus.h" #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 sni_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) + { + if (msg == WINE_SYSTRAY_NOTIFY_ICON) + { + switch (wparam) + { + case NIM_ADD: + return snidrv_add_notify_icon( (const NOTIFYICONDATAW *)data ); + case NIM_MODIFY: + return snidrv_modify_notify_icon( (const NOTIFYICONDATAW *)data ); + case NIM_DELETE: + return snidrv_delete_notify_icon( hwnd, ((const NOTIFYICONDATAW *)data)->uID ); + case NIM_SETVERSION: + return snidrv_set_notify_icon_version( hwnd, ((const NOTIFYICONDATAW *)data)->uID, ((const NOTIFYICONDATAW *)data)->uVersion ); + default: + FIXME( "Unknown NtUserSystemTrayCall NotifyIcon msg type %#x\n", (unsigned int)wparam ); + break; + } + } + else if (msg == WINE_SYSTRAY_RUN_LOOP) + return snidrv_run_loop(); + else if (msg == WINE_SYSTRAY_CLEANUP_ICONS) + return snidrv_cleanup_notify_icons( hwnd ); + } +#endif + switch (msg) { case WINE_SYSTRAY_NOTIFY_ICON:
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 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 003bceff122..805a13fcf13 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -93,6 +93,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 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 f794a3ba752..a828355a004 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -531,6 +531,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 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 428df70755e..8a8c00833fb 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 serg.cherniadjev@gmail.com
--- dlls/win32u/snidrv/dbus.c | 407 +++++++++++++++++++++++++++++++++++- dlls/win32u/snidrv/snidrv.h | 3 + 2 files changed, 407 insertions(+), 3 deletions(-)
diff --git a/dlls/win32u/snidrv/dbus.c b/dlls/win32u/snidrv/dbus.c index 03f13c322a5..8a9c93ef1b5 100644 --- a/dlls/win32u/snidrv/dbus.c +++ b/dlls/win32u/snidrv/dbus.c @@ -127,6 +127,7 @@ struct tray_icon 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; @@ -140,17 +141,37 @@ 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* watcher_interface_name = "org.kde.StatusNotifierWatcher"; 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"; @@ -182,6 +203,11 @@ static void sni_finalize(void) free(status_notifier_dst_path); }
+static void notifications_finalize(void) +{ + free(notifications_dst_path); +} + static void dbus_finalize(void) { if (global_connection != NULL) @@ -200,6 +226,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; @@ -228,12 +256,15 @@ static void snidrv_once_initialize(void) 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) @@ -242,6 +273,12 @@ BOOL snidrv_init(void) 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; @@ -311,6 +348,10 @@ static const char* dbus_name_owning_match = "type='signal'," "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); @@ -363,8 +404,42 @@ static DBusHandlerResult name_owner_filter( DBusConnection *ctx, DBusMessage *ms 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; } @@ -445,6 +520,7 @@ static BOOL get_notifier_watcher_owner(void)
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); @@ -459,6 +535,263 @@ err: 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]; @@ -1147,7 +1480,16 @@ BOOL snidrv_add_notify_icon(const NOTIFYICONDATAW* icon_data) 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)) { @@ -1262,6 +1604,13 @@ BOOL snidrv_modify_notify_icon( const NOTIFYICONDATAW* icon_data ) 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); @@ -1299,4 +1648,56 @@ BOOL snidrv_cleanup_notify_icons(HWND owner) 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; +} #endif diff --git a/dlls/win32u/snidrv/snidrv.h b/dlls/win32u/snidrv/snidrv.h index 5fd1dcfc279..d0f137b24a7 100644 --- a/dlls/win32u/snidrv/snidrv.h +++ b/dlls/win32u/snidrv/snidrv.h @@ -30,6 +30,7 @@
/* 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); @@ -39,4 +40,6 @@ 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
From: Sergei Chernyadyev serg.cherniadjev@gmail.com
--- dlls/win32u/systray.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/dlls/win32u/systray.c b/dlls/win32u/systray.c index 805a13fcf13..f914cd241d3 100644 --- a/dlls/win32u/systray.c +++ b/dlls/win32u/systray.c @@ -35,6 +35,7 @@ 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 ) @@ -70,6 +71,18 @@ LRESULT system_tray_call( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, voi else if (msg == WINE_SYSTRAY_CLEANUP_ICONS) return snidrv_cleanup_notify_icons( hwnd ); } + + 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)
On Mon Dec 4 18:22:38 2023 +0000, Rémi Bernon wrote:
Okay. Looks like libs like `libappindicator`, which requires Gtk but is used by a lot of software (trying to remove it from my DE requires to uninstall all gnome from the system), also targets that `org.kde` interface directly, so let's consider it to be acceptable for now (though we indeed want only one interface name).
Ok, removed freedesktop's interface.
On Mon Dec 4 15:08:39 2023 +0000, Rémi Bernon wrote:
I still don't find it useful, it adds more code than it removes, so not a great factoring IMO.
reverted
Chih-Hsuan Yen (@yan12125) commented about dlls/win32u/snidrv/dbus.c:
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);
Is it a good idea to assign `tiptext` to SNI `Title` property? SNI `ToolTip` property sounds a better target.
Per [1], `Title` is "a name that describes the application", while `tiptext` may be something random.
[1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNot... (yeah I know this MR is for `org.kde.StatusNotifierItem` instead of `org.freedesktop.StatusNotifierItem`, but I cannot find an authoritative description for the former)
On Mon Mar 4 12:04:45 2024 +0000, Chih-Hsuan Yen wrote:
Is it a good idea to assign `tiptext` to SNI `Title` property? SNI `ToolTip` property sounds a better target. Per [1], `Title` is "a name that describes the application", while `tiptext` may be something random. [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNot... (yeah I know this MR is for `org.kde.StatusNotifierItem` instead of `org.freedesktop.StatusNotifierItem`, but I cannot find an authoritative description for the former)
Ok, when I initially implemented this, I thought that tooltip requires using the image and description text alongside with the title, so I used title property.
If it's possible to use only tooltip title, then it's rly a better target.
On Sat Mar 9 08:20:35 2024 +0000, Cherser-s wrote:
Ok, when I initially implemented this, I thought that tooltip requires using the image and description text alongside with the title, so I used title property. If it's possible to use only tooltip title, then it's rly a better target.
Regarding the dbus part, is it decided that a separate driver is needed? I am thinking of attempting to approach suspend / resume notifications support which have to be based on dbus as event source. Can't we just have dbus interface in win32u.so instead of adding separate drivers on every occasion? @rbernon @jacek any opinion?
On Fri Aug 9 16:35:45 2024 +0000, Paul Gofman wrote:
Regarding the dbus part, is it decided that a separate driver is needed? I am thinking of attempting to approach suspend / resume notifications support which have to be based on dbus as event source. Can't we just have dbus interface in win32u.so instead of adding separate drivers on every occasion? @rbernon @jacek any opinion?
Sorry, I realized my question is a bit off, there is no driver for dbus interfacing per se. I'd rather want to ask what if I try to add just a dbus.c to server any possible dbus interaction in the future, so whatever win32u part is interested in interacting with it would just call the functions directly, without introducing a generic internal driver interfaces on this level?
On Fri Aug 9 16:53:22 2024 +0000, Paul Gofman wrote:
Sorry, I realized my question is a bit off, there is no driver for dbus interfacing per se. I'd rather want to ask what if I try to add just a dbus.c to server any possible dbus interaction in the future, so whatever win32u part is interested in interacting with it would just call the functions directly, without introducing a generic internal driver interfaces on this level?
In the light of the discussions that happened around this, and given that SNI apparently requires a separate connection for every systray icon, I'd say that having DBus connections in wineserver is probably a good idea because it's already able to multiplex them right away. Then I'm not sure how well that idea of having DBus connections, and multiple of them in wineserver, will be received.
On Fri Aug 9 17:25:37 2024 +0000, Rémi Bernon wrote:
In the light of the discussions that happened around this, and given that SNI apparently requires a separate connection for every systray icon, I'd say that having DBus connections in wineserver is probably a good idea because it's already able to multiplex them right away. Then I'm not sure how well that idea of having DBus connections, and multiple of them in wineserver, will be received.
Well, it is maybe too specific for wineserver? And there are probably not so much systrays icons even on Windows, do we need such an optimization?
On Fri Aug 9 17:29:22 2024 +0000, Paul Gofman wrote:
Well, it is maybe too specific for wineserver? And there are probably not so much systrays icons even on Windows, do we need such an optimization?
IMO the best solution is to move dbus connections and platform-specific systray handling (like in here and MacOS) to client applications without involving wineserver at all. cuz the only reason that explorer.exe has to handle systray is due to built-in wine systray window that handles all tray items inside, cant say for sure about XEMBED implementation.
On Fri Aug 9 18:10:45 2024 +0000, Cherser-s wrote:
IMO the best solution is to move dbus connections and platform-specific systray handling (like in here and MacOS) to client applications without involving wineserver at all. cuz the only reason that explorer.exe has to handle systray is due to built-in wine systray window that handles all tray items inside, cant say for sure about XEMBED implementation.
Yes, unless maybe Rémi has some nice and neat solution in mind which makes it simple I'd guess involving wineserver only complicates the matter? For power notifications that could in principle also be handled in explorer only using the server for subscription management and notification broadcasting.
On Fri Aug 9 18:16:00 2024 +0000, Paul Gofman wrote:
Yes, unless maybe Rémi has some nice and neat solution in mind which makes it simple I'd guess involving wineserver only complicates the matter? For power notifications that could in principle also be handled in explorer only using the server for subscription management and notification broadcasting.
Hmmmm, actually using wineserver isn't a bad idea yet it requires a lot of refactoring AND moving everything else that isnt builtin wine tray window away from explorer.exe as well.