From: Sergei Chernyadyev 1892-Cherser-s@users.noreply.gitlab.winehq.org
--- configure | 10 +- configure.ac | 9 +- dlls/winesni.drv/Makefile.in | 13 + dlls/winesni.drv/dbus.c | 1093 +++++++++++++++++++++++++++++ dlls/winesni.drv/dllmain.c | 48 ++ dlls/winesni.drv/image.c | 166 +++++ dlls/winesni.drv/snidrv_dll.h | 33 + dlls/winesni.drv/systray.c | 41 ++ dlls/winesni.drv/unixlib.c | 81 +++ dlls/winesni.drv/unixlib.h | 54 ++ dlls/winesni.drv/version.rc | 22 + dlls/winesni.drv/winesni.drv.spec | 2 + 12 files changed, 1566 insertions(+), 6 deletions(-) create mode 100644 dlls/winesni.drv/Makefile.in create mode 100644 dlls/winesni.drv/dbus.c create mode 100644 dlls/winesni.drv/dllmain.c create mode 100644 dlls/winesni.drv/image.c create mode 100644 dlls/winesni.drv/snidrv_dll.h create mode 100644 dlls/winesni.drv/systray.c create mode 100644 dlls/winesni.drv/unixlib.c create mode 100644 dlls/winesni.drv/unixlib.h create mode 100644 dlls/winesni.drv/version.rc create mode 100644 dlls/winesni.drv/winesni.drv.spec
diff --git a/configure b/configure index 93cdaa0a365..f5c2825cf9b 100755 --- a/configure +++ b/configure @@ -1485,6 +1485,7 @@ enable_wineusb_sys enable_winevulkan enable_winewayland_drv enable_winex11_drv +enable_winesni_drv enable_winexinput_sys enable_wing32 enable_winhttp @@ -2445,7 +2446,7 @@ Optional Packages: --without-capi do not use CAPI (ISDN support) --without-coreaudio do not use the CoreAudio sound support --without-cups do not use CUPS - --without-dbus do not use DBus (dynamic device support) + --without-dbus do not use DBus (dynamic device support, SNI tray) --with-float-abi=abi specify the ABI (soft|softfp|hard) for ARM platforms --without-fontconfig do not use fontconfig --without-freetype do not use the FreeType library @@ -16115,9 +16116,11 @@ fi
CPPFLAGS=$ac_save_CPPFLAGS
+else + enable_winesni_drv=no fi case $host_os in - darwin*|macosx*) ;; + darwin*|macosx*) enable_winesni_drv=no ;; *) if test "x$ac_cv_lib_soname_dbus_1" = "x" then : case "x$with_dbus" in @@ -16126,7 +16129,7 @@ then : *) as_fn_error $? "libdbus ${notice_platform}development files not found, no dynamic device support. This is an error since --with-dbus was requested." "$LINENO" 5 ;; esac - +enable_winesni_drv=${enable_winesni_drv:-no} fi ;; esac
@@ -21964,6 +21967,7 @@ wine_fn_config_makefile dlls/wineusb.sys enable_wineusb_sys wine_fn_config_makefile dlls/winevulkan enable_winevulkan wine_fn_config_makefile dlls/winewayland.drv enable_winewayland_drv wine_fn_config_makefile dlls/winex11.drv enable_winex11_drv +wine_fn_config_makefile dlls/winesni.drv enable_winesni_drv wine_fn_config_makefile dlls/winexinput.sys enable_winexinput_sys wine_fn_config_makefile dlls/wing.dll16 enable_win16 wine_fn_config_makefile dlls/wing32 enable_wing32 diff --git a/configure.ac b/configure.ac index dd8520f6f48..1b8cb27857b 100644 --- a/configure.ac +++ b/configure.ac @@ -28,7 +28,7 @@ AC_ARG_WITH(alsa, AS_HELP_STRING([--without-alsa],[do not use the Alsa soun AC_ARG_WITH(capi, AS_HELP_STRING([--without-capi],[do not use CAPI (ISDN support)])) AC_ARG_WITH(coreaudio, AS_HELP_STRING([--without-coreaudio],[do not use the CoreAudio sound support])) AC_ARG_WITH(cups, AS_HELP_STRING([--without-cups],[do not use CUPS])) -AC_ARG_WITH(dbus, AS_HELP_STRING([--without-dbus],[do not use DBus (dynamic device support)])) +AC_ARG_WITH(dbus, AS_HELP_STRING([--without-dbus],[do not use DBus (dynamic device support, SNI tray)])) AC_ARG_WITH(float-abi, AS_HELP_STRING([--with-float-abi=abi],[specify the ABI (soft|softfp|hard) for ARM platforms])) AC_ARG_WITH(fontconfig,AS_HELP_STRING([--without-fontconfig],[do not use fontconfig])) AC_ARG_WITH(freetype, AS_HELP_STRING([--without-freetype],[do not use the FreeType library])) @@ -1405,11 +1405,13 @@ then [AC_CHECK_HEADER([dbus/dbus.h], [WINE_CHECK_SONAME(dbus-1, dbus_connection_close,,[DBUS_CFLAGS=""],[$DBUS_LIBS])], [DBUS_CFLAGS=""])]) +else + enable_winesni_drv=no fi case $host_os in - darwin*|macosx*) ;; + darwin*|macosx*) enable_winesni_drv=no ;; *) WINE_NOTICE_WITH(dbus,[test "x$ac_cv_lib_soname_dbus_1" = "x"], - [libdbus ${notice_platform}development files not found, no dynamic device support.]) ;; + [libdbus ${notice_platform}development files not found, no dynamic device support.], [enable_winesni_drv]) ;; esac
dnl **** Check for libgnutls **** @@ -3173,6 +3175,7 @@ WINE_CONFIG_MAKEFILE(dlls/wineusb.sys) WINE_CONFIG_MAKEFILE(dlls/winevulkan) WINE_CONFIG_MAKEFILE(dlls/winewayland.drv) WINE_CONFIG_MAKEFILE(dlls/winex11.drv) +WINE_CONFIG_MAKEFILE(dlls/winesni.drv) WINE_CONFIG_MAKEFILE(dlls/winexinput.sys) WINE_CONFIG_MAKEFILE(dlls/wing.dll16,enable_win16) WINE_CONFIG_MAKEFILE(dlls/wing32) diff --git a/dlls/winesni.drv/Makefile.in b/dlls/winesni.drv/Makefile.in new file mode 100644 index 00000000000..ed73bf9092b --- /dev/null +++ b/dlls/winesni.drv/Makefile.in @@ -0,0 +1,13 @@ +MODULE = winesni.drv +UNIXLIB = winesni.so +IMPORTS = uuid win32u +UNIX_CFLAGS = $(DBUS_CFLAGS) $(HAL_CFLAGS) +UNIX_LIBS = -lwin32u + +SOURCES = \ + dllmain.c \ + unixlib.c \ + dbus.c \ + systray.c \ + image.c \ + version.rc \ diff --git a/dlls/winesni.drv/dbus.c b/dlls/winesni.drv/dbus.c new file mode 100644 index 00000000000..075ae5a43c5 --- /dev/null +++ b/dlls/winesni.drv/dbus.c @@ -0,0 +1,1093 @@ +/* + * DBus tray support + * + * Copyright 2023 Sergei Chernyadyev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include "unixlib.h" +#include "windef.h" +#include "winbase.h" +#include "winuser.h" + +#include <pthread.h> +#include "shellapi.h" +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <dlfcn.h> + +#include <poll.h> + +#include <dbus/dbus.h> + +#include "wine/gdi_driver.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(winesni); + +#define DBUS_FUNCS \ + DO_FUNC(dbus_bus_add_match); \ + DO_FUNC(dbus_bus_get); \ + DO_FUNC(dbus_bus_get_private); \ + DO_FUNC(dbus_bus_add_match); \ + DO_FUNC(dbus_bus_remove_match); \ + DO_FUNC(dbus_bus_get_unique_name); \ + DO_FUNC(dbus_connection_add_filter); \ + DO_FUNC(dbus_connection_read_write); \ + DO_FUNC(dbus_connection_dispatch); \ + DO_FUNC(dbus_connection_get_dispatch_status); \ + DO_FUNC(dbus_connection_read_write_dispatch); \ + DO_FUNC(dbus_connection_remove_filter); \ + DO_FUNC(dbus_connection_send); \ + DO_FUNC(dbus_connection_send_with_reply); \ + DO_FUNC(dbus_connection_send_with_reply_and_block); \ + DO_FUNC(dbus_connection_flush); \ + DO_FUNC(dbus_connection_try_register_object_path); \ + DO_FUNC(dbus_connection_unregister_object_path); \ + DO_FUNC(dbus_connection_list_registered); \ + DO_FUNC(dbus_connection_close); \ + DO_FUNC(dbus_connection_unref); \ + DO_FUNC(dbus_connection_get_object_path_data); \ + DO_FUNC(dbus_connection_get_unix_fd); \ + DO_FUNC(dbus_error_free); \ + DO_FUNC(dbus_error_init); \ + DO_FUNC(dbus_error_is_set); \ + DO_FUNC(dbus_set_error_from_message); \ + DO_FUNC(dbus_free_string_array); \ + DO_FUNC(dbus_message_get_args); \ + DO_FUNC(dbus_message_get_interface); \ + DO_FUNC(dbus_message_get_member); \ + DO_FUNC(dbus_message_get_path); \ + DO_FUNC(dbus_message_get_type); \ + DO_FUNC(dbus_message_is_signal); \ + DO_FUNC(dbus_message_iter_append_basic); \ + DO_FUNC(dbus_message_iter_get_arg_type); \ + DO_FUNC(dbus_message_iter_get_basic); \ + DO_FUNC(dbus_message_iter_append_fixed_array); \ + DO_FUNC(dbus_message_iter_get_fixed_array); \ + DO_FUNC(dbus_message_iter_init); \ + DO_FUNC(dbus_message_iter_init_append); \ + DO_FUNC(dbus_message_iter_next); \ + DO_FUNC(dbus_message_iter_recurse); \ + DO_FUNC(dbus_message_iter_open_container); \ + DO_FUNC(dbus_message_iter_close_container); \ + DO_FUNC(dbus_message_iter_abandon_container_if_open); \ + DO_FUNC(dbus_message_new_method_return); \ + DO_FUNC(dbus_message_new_method_call); \ + DO_FUNC(dbus_message_new_signal); \ + DO_FUNC(dbus_message_is_method_call); \ + DO_FUNC(dbus_message_new_error); \ + DO_FUNC(dbus_pending_call_block); \ + DO_FUNC(dbus_pending_call_unref); \ + DO_FUNC(dbus_pending_call_steal_reply); \ + DO_FUNC(dbus_threads_init_default); \ + DO_FUNC(dbus_message_unref) + +#define DO_FUNC(f) static typeof(f) * p_##f +DBUS_FUNCS; +#undef DO_FUNC + + +/* an individual systray icon */ +struct tray_icon +{ + 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 */ +}; + +static void* dbus_module = NULL; +static const char* kde_watcher_interface_name = "org.kde.StatusNotifierWatcher"; +static const char* freedesktop_watcher_interface_name = "org.freedesktop.StatusNotifierWatcher"; +static const char* watcher_interface_name = "org.kde.StatusNotifierWatcher"; + +static const char* kde_item_interface_name = "org.kde.StatusNotifierItem"; +static const char* freedesktop_item_interface_name = "org.freedesktop.StatusNotifierItem"; +static const char* item_interface_name = "org.kde.StatusNotifierItem"; + +static DBusConnection *connection; +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"; +/*TODO: sync it*/ +/*static pthread_mutex_t status_notifier_item_mutex = PTHREAD_MUTEX_INITIALIZER;*/ + +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 const char* dbus_name_owning_match = "type='signal'," + "interface='org.freedesktop.DBus'," + "sender='org.freedesktop.DBus'," + "member='NameOwnerChanged'"; +static const char* const tray_path_prefix = "/org/wine/tray"; +static const char* const tray_path_format = "/org/wine/tray/%zu/%d"; +static const char* const tray_path_fragment_format = "/org/wine/tray/%zu"; + +static BOOL register_notification_item(const char* object_name); + +static void register_items(DBusConnection *ctx) +{ + char** child_entries = NULL; + char** child_entry_ptr = NULL; + + if (!p_dbus_connection_list_registered(connection, tray_path_prefix, &child_entries) || child_entries == NULL) + goto fail; + + child_entry_ptr = child_entries; + while (*child_entry_ptr != NULL) + { + char object_window_name[64]; + char object_name[128]; + char** object_child_entries = NULL; + char** object_child_entry_ptr = NULL; + + snprintf(object_window_name, sizeof(object_window_name), "%s/%s", tray_path_prefix,*child_entry_ptr); + + if (!p_dbus_connection_list_registered(connection, object_window_name, &object_child_entries) || object_child_entries == NULL) + goto fail; + object_child_entry_ptr = object_child_entries; + while (*object_child_entry_ptr != NULL) + { + snprintf(object_name, sizeof(object_name), "%s/%s", object_window_name, *object_child_entry_ptr); + register_notification_item(object_name); + object_child_entry_ptr++; + } + + p_dbus_free_string_array(object_child_entries); + child_entry_ptr++; + } +fail: + p_dbus_free_string_array(child_entries); +} + +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 (status_notifier_dst_path != NULL && + status_notifier_dst_path[0] != '\0' && + strcmp(interface_name, watcher_interface_name) == 0) + { + old_path = status_notifier_dst_path; + status_notifier_dst_path = strdup(new_path); + free(old_path); + } + else if (status_notifier_dst_path == NULL || + status_notifier_dst_path[0] == '\0') + { + bool is_kde_interface = strcmp(interface_name, kde_watcher_interface_name) == 0; + bool is_freedesktop_interface = strcmp(interface_name, freedesktop_watcher_interface_name) == 0; + if (!is_kde_interface && !is_freedesktop_interface) goto cleanup; + + if (strcmp(interface_name, kde_watcher_interface_name) == 0) + { + watcher_interface_name = kde_watcher_interface_name; + item_interface_name = kde_item_interface_name; + } + else if (strcmp(interface_name, freedesktop_watcher_interface_name) == 0) + { + watcher_interface_name = freedesktop_watcher_interface_name; + item_interface_name = freedesktop_item_interface_name; + } + /* TODO: lock the mutex */ + old_path = status_notifier_dst_path; + status_notifier_dst_path = strdup(new_path); + free(old_path); + if (status_notifier_dst_path[0] != '\0') register_items(ctx); + } + } +cleanup: + p_dbus_error_free( &error ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +BOOL get_notifier_watcher_owner_for_interface(const char* interface_name, const char* sni_interface_name) +{ + 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_connection_flush(connection); + + 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; + } + else + { + TRACE("found notifier destination name %s\n", status_notifier_dest); + watcher_interface_name = interface_name; + item_interface_name = sni_interface_name; + status_notifier_dst_path = strdup(status_notifier_dest); + } + p_dbus_message_unref(msg); + return TRUE; +err: + p_dbus_message_unref(msg); + return FALSE; +} + +BOOL get_notifier_watcher_owner(void) +{ + DBusError error; + p_dbus_error_init( &error ); + if (!p_dbus_threads_init_default()) goto err; + + 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 err; + } + + if (!get_notifier_watcher_owner_for_interface(kde_watcher_interface_name, kde_item_interface_name) && + !get_notifier_watcher_owner_for_interface(freedesktop_watcher_interface_name, freedesktop_item_interface_name)) + { + WARN("failed to query watcher interface owner\n"); + goto err; + } + + p_dbus_connection_add_filter( connection, name_owner_filter, NULL, NULL ); + p_dbus_bus_add_match( 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); + p_dbus_error_free( &error); + goto err; + } + return TRUE; + +err: + return FALSE; +} + +void dbus_finalize() +{ + if (connection != NULL) + { + p_dbus_connection_close(connection); + p_dbus_connection_unref(connection); + } + if (dbus_module != NULL) + { + dlclose(dbus_module); + } +} + +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_%d", 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_connection_flush(conn); + 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); + + 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); + + if (!p_dbus_connection_send(conn, reply, &serial)) goto fail; + p_dbus_connection_flush(conn); + p_dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; +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; + DBusError error; + p_dbus_error_init(&error); + if (!p_dbus_message_get_args( message, &error, DBUS_TYPE_STRING, &interface_name, + DBUS_TYPE_STRING, &property_name, DBUS_TYPE_INVALID )) + { + DBusHandlerResult ret = notification_send_error (conn, message, error.name, error.message); + p_dbus_error_free( &error); + return ret; + } + + if (strcmp(interface_name, freedesktop_item_interface_name) != 0 && + strcmp(interface_name, kde_item_interface_name) != 0) + { + char error_message[128]; + snprintf(error_message, sizeof(error_message), "unsupported interface %s", interface_name); + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.UnknownProperty", error_message); + } + 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); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + 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); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + 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); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + 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); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + handle_status(conn, &vIter, icon); + p_dbus_message_iter_close_container(&iter, &vIter); + } + else + { + char error_message[128]; + 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); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + p_dbus_connection_flush(conn); + p_dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + else if (p_dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "GetAll")) + { + const char* interface_name = ""; + DBusMessageIter args; + if (!p_dbus_message_iter_init(message, &args)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + else if (DBUS_TYPE_STRING != p_dbus_message_iter_get_arg_type(&args)) + { + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + } + else + p_dbus_message_iter_get_basic(&args, &interface_name); + + if (strcmp(kde_item_interface_name, interface_name) == 0 || + strcmp(freedesktop_item_interface_name, interface_name) == 0) + return get_all_tray_properties(conn, message, icon); + else + return notification_send_error(conn, message, "org.freedesktop.DBus.Error.UnknownInterface", "Call to Get has wrong args"); + + } + else if (p_dbus_message_is_method_call(message, kde_item_interface_name, "ContextMenu") || + p_dbus_message_is_method_call(message, freedesktop_item_interface_name, "ContextMenu")) + { + int x,y; + DBusMessageIter args; + if (!p_dbus_message_iter_init(message, &args)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &x); + + if (!p_dbus_message_iter_next(&args) || DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &y); + + notify_owner( icon, WM_RBUTTONDOWN, (unsigned short) x, (unsigned short) y); + if (icon->version > 0) + notify_owner( icon, WM_CONTEXTMENU, (unsigned short) x, (unsigned short) y); + } + else if (p_dbus_message_is_method_call(message, kde_item_interface_name, "Activate") || + p_dbus_message_is_method_call(message, freedesktop_item_interface_name, "Activate")) + { + int x,y; + DBusMessageIter args; + if (!p_dbus_message_iter_init(message, &args)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &x); + + if (!p_dbus_message_iter_next(&args) || DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &y); + + notify_owner( icon, WM_LBUTTONDOWN, (unsigned short) x, (unsigned short) y); + if (icon->version > 0) notify_owner( icon, NIN_SELECT, (unsigned short) x, (unsigned short) y); + } + else if (p_dbus_message_is_method_call(message, kde_item_interface_name, "SecondaryActivate") || + p_dbus_message_is_method_call(message, freedesktop_item_interface_name, "SecondaryActivate")) + { + int x,y; + DBusMessageIter args; + if (!p_dbus_message_iter_init(message, &args)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + if (DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &x); + + if (!p_dbus_message_iter_next(&args) || DBUS_TYPE_INT32 != p_dbus_message_iter_get_arg_type(&args)) + return notification_send_error (conn, message, "org.freedesktop.DBus.Error.InvalidArgs", "Call to Get has wrong args"); + else + p_dbus_message_iter_get_basic(&args, &y); + notify_owner( icon, WM_MBUTTONDOWN, (unsigned short) x, (unsigned short) y); + } + else if (p_dbus_message_is_method_call(message, kde_item_interface_name, "Scroll") || + p_dbus_message_is_method_call(message, freedesktop_item_interface_name, "Scroll")) + { + /* do nothing */ + } + else if (p_dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL) + return notification_send_error (conn, message, "DBus.Error.UnknownMethod", "Unknown method"); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +void unregister_notification_handler(DBusConnection* conn, void* user_data) +{ + struct tray_icon* icon = (struct tray_icon*)user_data; + + if (icon->hIcon) NtUserDestroyCursor(icon->hIcon, 0); + if (icon->icon_bitmap) free(icon->icon_bitmap); + + free(icon); +} + +const DBusObjectPathVTable notification_vtable = +{ + .message_function = notification_message_handler, + .unregister_function = unregister_notification_handler +}; + + +void run_dbus_loop(void) +{ + int dbus_conn_fd; + struct pollfd fd_info; + p_dbus_connection_get_unix_fd(connection, &dbus_conn_fd); + + fd_info = (struct pollfd) { + .fd = dbus_conn_fd, + .events = POLLIN, + }; + while (true) + { + /* TODO: utilize DBusWatch */ + poll(&fd_info, 1, -1); + /* non blocking read of the next available message */ + if (!p_dbus_connection_read_write(connection, 0)) break; + + while ( p_dbus_connection_get_dispatch_status ( connection ) == DBUS_DISPATCH_DATA_REMAINS ) + { + p_dbus_connection_dispatch ( connection ) ; + } + } +} + +static BOOL register_notification_item(const char* object_name) +{ + DBusMessageIter args; + DBusPendingCall* pending; + DBusMessage* msg = NULL; + DBusError error; + char service_object_name[256]; + if (strcmp(watcher_interface_name, freedesktop_watcher_interface_name) == 0) + { + /* prepend source unique name */ + snprintf(service_object_name, sizeof(service_object_name), "%s%s", p_dbus_bus_get_unique_name(connection), object_name); + object_name = service_object_name; + } + p_dbus_error_init( &error ); + msg = p_dbus_message_new_method_call(status_notifier_dst_path, + "/StatusNotifierWatcher", + watcher_interface_name , + "RegisterStatusNotifierItem"); + if (!msg) goto err; + + p_dbus_message_iter_init_append(msg, &args); + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &object_name )) + goto err; + + if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1)) + goto err; + if (!pending) goto err; + + p_dbus_connection_flush(connection); + 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(const char* object_path, 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_connection_flush(connection); + + 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 add_icon(const NOTIFYICONDATAW* icon_data) +{ + char object_name[64]; + struct tray_icon* icon = NULL; + DBusError error; + bool registered = false; + p_dbus_error_init( &error ); + snprintf(object_name, sizeof(object_name), tray_path_format, (size_t)icon_data->hWnd, icon_data->uID); + 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; + if (icon_data->uFlags & NIF_ICON) + { + if (!get_icon_data(icon_data, icon)) 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_name, ¬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(object_name)) + { + WARN("failed to register item %s\n", object_name); + p_dbus_connection_unregister_object_path(connection, object_name); + goto fail; + } + return TRUE; +fail: + if (icon && icon->hIcon) NtUserDestroyCursor(icon->hIcon, 0); + if (icon && icon->icon_bitmap) free(icon->icon_bitmap); + free(icon); + if (registered) + p_dbus_connection_unregister_object_path(connection, object_name); + return FALSE; +} + +BOOL delete_icon(const NOTIFYICONDATAW* icon_data) +{ + DBusError error; + char object_name[64]; + p_dbus_error_init( &error ); + snprintf(object_name, sizeof(object_name), tray_path_format, (size_t)icon_data->hWnd, icon_data->uID); + + if (!p_dbus_connection_unregister_object_path(connection, object_name)) + { + WARN("failed register object %s: %s\n", error.name, error.message); + p_dbus_error_free( &error ); + return FALSE; + } + return TRUE; +} + +BOOL modify_icon(const NOTIFYICONDATAW* icon_data) +{ + DBusError error; + char object_path[64]; + struct tray_icon* icon; + UINT new_state; + p_dbus_error_init( &error ); + snprintf(object_path, sizeof(object_path), tray_path_format, (size_t)icon_data->hWnd, icon_data->uID); + + if (!p_dbus_connection_get_object_path_data(connection, object_path, (void**)&icon)) + return FALSE; + + if (icon_data->uFlags & NIF_ICON) + { + if (!get_icon_data(icon_data, icon)) + goto err; + if (!send_signal_to_item(object_path, "NewIcon")) + goto err; + } + + 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; + if (!send_signal_to_item(object_path, "NewStatus")) goto err; + } + } + + if (icon_data->uFlags & NIF_TIP) + { + ntdll_wcstoumbs(icon_data->szTip, wcslen(icon_data->szTip) + 1, icon->tiptext, ARRAY_SIZE(icon->tiptext), FALSE); + if (!send_signal_to_item(object_path, "NewTitle")) goto err; + } + return TRUE; +err: + return FALSE; +} + +BOOL set_icon_version(const NOTIFYICONDATAW* icon_data) +{ + DBusError error; + char object_path[64]; + struct tray_icon* icon; + p_dbus_error_init( &error ); + snprintf(object_path, sizeof(object_path), tray_path_format, (size_t)icon_data->hWnd, icon_data->uID); + + if (!p_dbus_connection_get_object_path_data(connection, object_path, (void**)&icon)) + return FALSE; + + icon->version = icon_data->uVersion; + return TRUE; +} + +BOOL cleanup_icons(HWND owner) +{ + char object_prefix[64]; + char** child_entries = NULL; + char** child_entry_ptr = NULL; + snprintf(object_prefix, sizeof(object_prefix), tray_path_fragment_format, (size_t)owner); + + if (!p_dbus_connection_list_registered(connection, object_prefix, &child_entries) || child_entries == NULL) + return FALSE; + + child_entry_ptr = child_entries; + while (*child_entry_ptr != NULL) + { + char object_name[128]; + snprintf(object_name, sizeof(object_name), "%s/%s", object_prefix, *child_entry_ptr); + p_dbus_connection_unregister_object_path(connection, object_name); + child_entry_ptr++; + } + + p_dbus_free_string_array(child_entries); + return TRUE; +} diff --git a/dlls/winesni.drv/dllmain.c b/dlls/winesni.drv/dllmain.c new file mode 100644 index 00000000000..abd0f9df7a3 --- /dev/null +++ b/dlls/winesni.drv/dllmain.c @@ -0,0 +1,48 @@ +/* + * winesni.drv entry points + * + * 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 + */ + +#include "snidrv_dll.h" +#include "wine/debug.h" + + +static DWORD WINAPI dbus_sni_read_thread(void* arg) { + SNIDRV_UNIX_CALL(run_loop, NULL); + /* This thread terminates only if an unrecoverable error occured during + * event reading. */ + TerminateProcess(GetCurrentProcess(), 1); + return 0; +} + +BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) +{ + if (reason != DLL_PROCESS_ATTACH) return TRUE; + + DisableThreadLibraryCalls(instance); + if (__wine_init_unix_call()) return FALSE; + + if (SNIDRV_UNIX_CALL(init, NULL)) { + return FALSE; + } + + /* Read dbus messages from a dedicated thread. */ + CloseHandle(CreateThread(NULL, 0, dbus_sni_read_thread, NULL, 0, NULL)); + + return TRUE; +} diff --git a/dlls/winesni.drv/image.c b/dlls/winesni.drv/image.c new file mode 100644 index 00000000000..2f260daff64 --- /dev/null +++ b/dlls/winesni.drv/image.c @@ -0,0 +1,166 @@ +/* + * DBus tray support + * + * Copyright 2023 Sergei Chernyadyev + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include "unixlib.h" +#include "stdlib.h" +#include "ntuser.h" +#include "ntgdi.h" + +/*********************************************************************** + * get_mono_icon_argb + * + * Return a monochrome icon/cursor bitmap bits in ARGB format. + */ +static unsigned int *get_mono_icon_argb( HDC hdc, HBITMAP bmp, unsigned int *width, unsigned int *height ) +{ + BITMAP bm; + char *mask; + unsigned int i, j, stride, mask_size, bits_size, *bits = NULL, *ptr; + + if (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return NULL; + stride = ((bm.bmWidth + 15) >> 3) & ~1; + mask_size = stride * bm.bmHeight; + if (!(mask = malloc( mask_size ))) return NULL; + if (!NtGdiGetBitmapBits( bmp, mask_size, mask )) goto done; + + bm.bmHeight /= 2; + bits_size = bm.bmWidth * bm.bmHeight * sizeof(*bits); + if (!(bits = malloc( bits_size ))) goto done; + + ptr = bits; + for (i = 0; i < bm.bmHeight; i++) + for (j = 0; j < bm.bmWidth; j++, ptr++) + { + int and = ((mask[i * stride + j / 8] << (j % 8)) & 0x80); + int xor = ((mask[(i + bm.bmHeight) * stride + j / 8] << (j % 8)) & 0x80); + if (!xor && and) + *ptr = 0; + else if (xor && !and) + *ptr = 0xffffffff; + else + /* we can't draw "invert" pixels, so render them as black instead */ +#ifdef WORDS_BIGENDIAN + *ptr = 0xff000000; +#else + *ptr = 0x000000ff; +#endif + } + + *width = bm.bmWidth; + *height = bm.bmHeight; + +done: + free( mask ); + return bits; +} + +/*********************************************************************** + * get_bitmap_argb + * + * Return the bitmap bits in ARGB format. Helper for setting icons and cursors. + */ +static unsigned int *get_bitmap_argb( HDC hdc, HBITMAP color, HBITMAP mask, unsigned int *width, + unsigned int *height ) +{ + char buffer[FIELD_OFFSET( BITMAPINFO, bmiColors[256] )]; + BITMAPINFO *info = (BITMAPINFO *)buffer; + BITMAP bm; + unsigned int *ptr, *bits = NULL; + unsigned char *mask_bits = NULL; + int i, j; + BOOL has_alpha = FALSE; + + if (!color) return get_mono_icon_argb( hdc, mask, width, height ); + + if (!NtGdiExtGetObjectW( color, sizeof(bm), &bm )) return NULL; + info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info->bmiHeader.biWidth = bm.bmWidth; + info->bmiHeader.biHeight = -bm.bmHeight; + info->bmiHeader.biPlanes = 1; + info->bmiHeader.biBitCount = 32; + info->bmiHeader.biCompression = BI_RGB; + info->bmiHeader.biSizeImage = bm.bmWidth * bm.bmHeight * 4; + info->bmiHeader.biXPelsPerMeter = 0; + info->bmiHeader.biYPelsPerMeter = 0; + info->bmiHeader.biClrUsed = 0; + info->bmiHeader.biClrImportant = 0; + if (!(bits = malloc( bm.bmWidth * bm.bmHeight * sizeof(unsigned int) ))) + goto failed; + if (!NtGdiGetDIBitsInternal( hdc, color, 0, bm.bmHeight, bits, info, DIB_RGB_COLORS, 0, 0 )) + goto failed; + + *width = bm.bmWidth; + *height = bm.bmHeight; + + for (i = 0; i < bm.bmWidth * bm.bmHeight; i++) + if ((has_alpha = (bits[i] & 0xff000000) != 0)) break; + + if (!has_alpha) + { + unsigned int width_bytes = (bm.bmWidth + 31) / 32 * 4; + /* generate alpha channel from the mask */ + info->bmiHeader.biBitCount = 1; + info->bmiHeader.biSizeImage = width_bytes * bm.bmHeight; + if (!(mask_bits = malloc( info->bmiHeader.biSizeImage ))) goto failed; + if (!NtGdiGetDIBitsInternal( hdc, mask, 0, bm.bmHeight, mask_bits, info, DIB_RGB_COLORS, 0, 0 )) + goto failed; + ptr = bits; + for (i = 0; i < bm.bmHeight; i++) + for (j = 0; j < bm.bmWidth; j++, ptr++) + if (!((mask_bits[i * width_bytes + j / 8] << (j % 8)) & 0x80)) *ptr |= 0xff000000; + free( mask_bits ); + } +#ifndef WORDS_BIGENDIAN + for (unsigned i = 0; i < bm.bmWidth * bm.bmHeight; i++) + { + bits[i] = ((bits[i] & 0xFF) << 24) | ((bits[i] & 0xFF00) << 8) | ((bits[i] & 0xFF0000) >> 8) | ((bits[i] & 0xFF000000) >> 24); + } +#endif + + return bits; + +failed: + free( bits ); + free( mask_bits ); + *width = *height = 0; + return NULL; +} + + +BOOL create_bitmap_from_icon(HANDLE icon, unsigned *p_width, unsigned *p_height, void** p_bits) +{ + ICONINFO info; + HDC hdc; + + if (!NtUserGetIconInfo(icon, &info, NULL, NULL, NULL, 0)) + return FALSE; + + hdc = NtGdiCreateCompatibleDC( 0 ); + *p_bits = get_bitmap_argb( hdc, info.hbmColor, info.hbmMask, p_width, p_height ); + NtGdiDeleteObjectApp( info.hbmMask ); + NtGdiDeleteObjectApp( hdc ); + return *p_bits != NULL; +} diff --git a/dlls/winesni.drv/snidrv_dll.h b/dlls/winesni.drv/snidrv_dll.h new file mode 100644 index 00000000000..c725efd4ea2 --- /dev/null +++ b/dlls/winesni.drv/snidrv_dll.h @@ -0,0 +1,33 @@ +/* + * SNI driver DLL definitions + * + * Copyright 2022 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_DLL_H +#define __WINE_SNIDRV_DLL_H + +#include <stdarg.h> +#include "windef.h" +#include "winbase.h" + +#include "unixlib.h" + +#define SNIDRV_UNIX_CALL(func, params) WINE_UNIX_CALL(snidrv_unix_func_ ## func, params) + + +#endif /* __WINE_SNIDRV_DLL_H */ diff --git a/dlls/winesni.drv/systray.c b/dlls/winesni.drv/systray.c new file mode 100644 index 00000000000..dd52e660002 --- /dev/null +++ b/dlls/winesni.drv/systray.c @@ -0,0 +1,41 @@ +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "snidrv_dll.h" +#include "shellapi.h" + +#include "wine/list.h" +#include "wine/debug.h" + + +WINE_DEFAULT_DEBUG_CHANNEL(systray); +/*********************************************************************** + * wine_notify_icon (X11DRV.@) + * + * Driver-side implementation of Shell_NotifyIcon. + */ +int CDECL wine_notify_icon( DWORD msg, NOTIFYICONDATAW *data ) +{ + BOOL ret = FALSE; + switch (msg) + { + case NIM_ADD: + ret = SNIDRV_UNIX_CALL(add_icon, data) == STATUS_SUCCESS; + break; + case NIM_DELETE: + ret = SNIDRV_UNIX_CALL(delete_icon, data) == STATUS_SUCCESS; + break; + case NIM_MODIFY: + ret = SNIDRV_UNIX_CALL(modify_icon, data) == STATUS_SUCCESS; + break; + case NIM_SETVERSION: + ret = SNIDRV_UNIX_CALL(set_icon_version, data) == STATUS_SUCCESS; + break; + case 0xdead: /* Wine extension: owner window has died */ + ret = SNIDRV_UNIX_CALL(cleanup_icons, data->hWnd ) == STATUS_SUCCESS; + break; + default: + FIXME( "unhandled tray message: %lu\n", msg ); + break; + } + return ret; +} diff --git a/dlls/winesni.drv/unixlib.c b/dlls/winesni.drv/unixlib.c new file mode 100644 index 00000000000..9352d54aad8 --- /dev/null +++ b/dlls/winesni.drv/unixlib.c @@ -0,0 +1,81 @@ +/* + * DBus tray initializer + * + * 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 <ntstatus.h> +#define WIN32_NO_STATUS +#include "unixlib.h" +#include "wine/unixlib.h" + +NTSTATUS snidrv_unix_init(void *arg) +{ + if (!load_dbus_functions()) goto err; + if (!get_notifier_watcher_owner()) goto err; + return STATUS_SUCCESS; +err: + dbus_finalize(); + return STATUS_UNSUCCESSFUL; +} + + +NTSTATUS snidrv_unix_run_loop(void* arg) +{ + run_dbus_loop(); + return STATUS_SUCCESS; +} + +NTSTATUS snidrv_unix_add_icon(void* icon_data) +{ + return add_icon((const NOTIFYICONDATAW*)icon_data) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; +} + +NTSTATUS snidrv_unix_delete_icon(void* icon_data) +{ + return delete_icon((const NOTIFYICONDATAW*)icon_data) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; +} + +NTSTATUS snidrv_unix_modify_icon(void* icon_data) +{ + return modify_icon((const NOTIFYICONDATAW*)icon_data) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; +} + +NTSTATUS snidrv_unix_set_icon_version(void* icon_data) +{ + return set_icon_version((const NOTIFYICONDATAW*)icon_data) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; +} + +NTSTATUS snidrv_unix_cleanup_icons(void* owner) +{ + return cleanup_icons((HWND)owner) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; +} + +const unixlib_entry_t __wine_unix_call_funcs[] = +{ + snidrv_unix_init, + snidrv_unix_run_loop, + snidrv_unix_add_icon, + snidrv_unix_delete_icon, + snidrv_unix_modify_icon, + snidrv_unix_set_icon_version, + snidrv_unix_cleanup_icons, +}; diff --git a/dlls/winesni.drv/unixlib.h b/dlls/winesni.drv/unixlib.h new file mode 100644 index 00000000000..4476a4cbff6 --- /dev/null +++ b/dlls/winesni.drv/unixlib.h @@ -0,0 +1,54 @@ +/* + * 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_UNIXLIB_H +#define __WINE_SNIDRV_UNIXLIB_H + +#include <stdarg.h> +#include "windef.h" +#include "winbase.h" +#include "winuser.h" +#include "shellapi.h" +#include "winternl.h" + +#include "wine/unixlib.h" + +enum snirv_unix_func +{ + snidrv_unix_func_init, + snidrv_unix_func_run_loop, + snidrv_unix_func_add_icon, + snidrv_unix_func_delete_icon, + snidrv_unix_func_modify_icon, + snidrv_unix_func_set_icon_version, + snidrv_unix_func_cleanup_icons, + snidrv_unix_func_count, +}; + +extern BOOL add_icon(const NOTIFYICONDATAW* icon_data) DECLSPEC_HIDDEN; +extern BOOL delete_icon(const NOTIFYICONDATAW* icon_data) DECLSPEC_HIDDEN; +extern BOOL modify_icon(const NOTIFYICONDATAW* icon_data) DECLSPEC_HIDDEN; +extern BOOL set_icon_version(const NOTIFYICONDATAW* icon_data) DECLSPEC_HIDDEN; +extern BOOL cleanup_icons(HWND owner) DECLSPEC_HIDDEN; + +void dbus_finalize(void); +void run_dbus_loop(void); +BOOL load_dbus_functions(void); +BOOL get_notifier_watcher_owner(void); +BOOL create_bitmap_from_icon(HANDLE icon, unsigned *p_width, unsigned *p_height, void** p_bits); +#endif /* __WINE_SNIDRV_UNIXLIB_H */ diff --git a/dlls/winesni.drv/version.rc b/dlls/winesni.drv/version.rc new file mode 100644 index 00000000000..452333da1e5 --- /dev/null +++ b/dlls/winesni.drv/version.rc @@ -0,0 +1,22 @@ +/* + * Copyright (c) 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 + */ + +#define WINE_FILEDESCRIPTION_STR "Wine DBUS SNI driver" +#define WINE_FILENAME_STR "winesni.drv" + +#include "wine/wine_common_ver.rc" diff --git a/dlls/winesni.drv/winesni.drv.spec b/dlls/winesni.drv/winesni.drv.spec new file mode 100644 index 00000000000..5f086f5c4e5 --- /dev/null +++ b/dlls/winesni.drv/winesni.drv.spec @@ -0,0 +1,2 @@ +# System tray +@ cdecl wine_notify_icon(long ptr)