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.
-- v9: configure: fix winesni.drv not getting disabled
From: Sergei Chernyadyev 1892-Cherser-s@users.noreply.gitlab.winehq.org
--- configure | 4 +- configure.ac | 5 +- dlls/winesni.drv/Makefile.in | 14 + dlls/winesni.drv/dbus.c | 1092 +++++++++++++++++++++++++++++ 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 | 76 ++ dlls/winesni.drv/unixlib.h | 54 ++ dlls/winesni.drv/version.rc | 22 + dlls/winesni.drv/winesni.drv.spec | 2 + 12 files changed, 1554 insertions(+), 3 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 ee24e91136a..81627094416 100755 --- a/configure +++ b/configure @@ -1484,6 +1484,7 @@ enable_wineusb_sys enable_winevulkan enable_winewayland_drv enable_winex11_drv +enable_winesni_drv enable_winexinput_sys enable_wing32 enable_winhttp @@ -16087,7 +16088,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
@@ -21925,6 +21926,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 0a6171e7fd3..fd215f99619 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])) @@ -1398,7 +1398,7 @@ fi case $host_os in darwin*|macosx*) ;; *) 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 **** @@ -3162,6 +3162,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..6c488d5210b --- /dev/null +++ b/dlls/winesni.drv/Makefile.in @@ -0,0 +1,14 @@ +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..1cb57543d8e --- /dev/null +++ b/dlls/winesni.drv/dbus.c @@ -0,0 +1,1092 @@ +/* + * 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_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);; + } + 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 some 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)) { + 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 unregister_notification_item(const char* object_name) +{ + DBusMessageIter args; + DBusMessage* msg; + DBusError error; + unsigned serial = 0; + char service_object_name[256]; + if (status_notifier_dst_path == NULL || status_notifier_dst_path[0] == '\0') { + return TRUE; + } + 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_signal("/StatusNotifierWatcher", + watcher_interface_name , + "StatusNotifierItemUnregistered"); + if (NULL == msg) { + return FALSE; + } + + p_dbus_message_iter_init_append(msg, &args); + if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &object_name )) { + return FALSE; + } + if (!p_dbus_connection_send (connection, msg, &serial)) { + return FALSE; + } + p_dbus_connection_flush(connection); + + p_dbus_message_unref(msg); + + return TRUE; +} + +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)) { + 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) +{ + ICONINFO icon_info; + void* bits = NULL; + unsigned width, height; + HICON new_icon = NULL; + memset(&icon_info, 0, sizeof(icon_info)); + 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); + + unregister_notification_item(object_name); + 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); + unregister_notification_item(object_name); + 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..0fd38fa409d --- /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..4a6063e614e --- /dev/null +++ b/dlls/winesni.drv/unixlib.c @@ -0,0 +1,76 @@ +/* + * 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)
From: Sergei Chernyadyev 1892-Cherser-s@users.noreply.gitlab.winehq.org
--- programs/explorer/desktop.c | 10 +++++++++- programs/explorer/explorer_private.h | 2 +- programs/explorer/systray.c | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/programs/explorer/desktop.c b/programs/explorer/desktop.c index 81eb0d1d01b..1d50fbb7034 100644 --- a/programs/explorer/desktop.c +++ b/programs/explorer/desktop.c @@ -1041,6 +1041,7 @@ void manage_desktop( WCHAR *arg ) MSG msg; HWND hwnd; HMODULE graphics_driver; + HMODULE remote_notification_driver; unsigned int width, height; WCHAR *cmdline = NULL, *driver = NULL; WCHAR *p = arg; @@ -1050,6 +1051,7 @@ void manage_desktop( WCHAR *arg ) HMODULE shell32; HANDLE thread; DWORD id; + void* notify_icon_ptr = NULL;
/* get the rest of the command line (if any) */ while (*p && !is_whitespace(*p)) p++; @@ -1123,7 +1125,13 @@ void manage_desktop( WCHAR *arg )
if (using_root) enable_shell = FALSE;
- initialize_systray( graphics_driver, using_root, enable_shell ); + remote_notification_driver = LoadLibraryW( L"winesni.drv" ); + if (remote_notification_driver) { + notify_icon_ptr = (void *)GetProcAddress( remote_notification_driver, "wine_notify_icon" ); + } else { + notify_icon_ptr = (void *)GetProcAddress( graphics_driver, "wine_notify_icon" ); + } + initialize_systray( notify_icon_ptr, using_root, enable_shell ); if (!using_root) initialize_launchers( hwnd );
if ((shell32 = LoadLibraryW( L"shell32.dll" )) && diff --git a/programs/explorer/explorer_private.h b/programs/explorer/explorer_private.h index 995e7eb803e..fe0a533a1ee 100644 --- a/programs/explorer/explorer_private.h +++ b/programs/explorer/explorer_private.h @@ -22,7 +22,7 @@ #define __WINE_EXPLORER_PRIVATE_H
extern void manage_desktop( WCHAR *arg ) DECLSPEC_HIDDEN; -extern void initialize_systray( HMODULE graphics_driver, BOOL using_root, BOOL enable_shell ) DECLSPEC_HIDDEN; +extern void initialize_systray( void* fn_notify_icon, BOOL using_root, BOOL enable_shell ) DECLSPEC_HIDDEN; extern void initialize_appbar(void) DECLSPEC_HIDDEN; extern void handle_parent_notify( HWND hwnd, WPARAM wp ) DECLSPEC_HIDDEN; extern void do_startmenu( HWND owner ) DECLSPEC_HIDDEN; diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 7790648d021..bdc6d2ea015 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -892,13 +892,13 @@ void handle_parent_notify( HWND hwnd, WPARAM wp ) }
/* this function creates the listener window */ -void initialize_systray( HMODULE graphics_driver, BOOL using_root, BOOL arg_enable_shell ) +void initialize_systray( void* fn_notify_icon, BOOL using_root, BOOL arg_enable_shell ) { WNDCLASSEXW class; static const WCHAR classname[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d',0}; RECT work_rect, primary_rect, taskbar_rect;
- if (using_root && graphics_driver) wine_notify_icon = (void *)GetProcAddress( graphics_driver, "wine_notify_icon" ); + if (using_root && fn_notify_icon) wine_notify_icon = (void *)fn_notify_icon;
icon_cx = GetSystemMetrics( SM_CXSMICON ) + 2*ICON_BORDER; icon_cy = GetSystemMetrics( SM_CYSMICON ) + 2*ICON_BORDER;
From: Sergei Chernyadyev 1892-Cherser-s@users.noreply.gitlab.winehq.org
--- configure | 2 +- configure.ac | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/configure b/configure index 81627094416..53210c3823a 100755 --- a/configure +++ b/configure @@ -16079,7 +16079,7 @@ CPPFLAGS=$ac_save_CPPFLAGS
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 diff --git a/configure.ac b/configure.ac index fd215f99619..6ab383ecc09 100644 --- a/configure.ac +++ b/configure.ac @@ -1394,9 +1394,11 @@ 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.], [enable_winesni_drv]) ;; esac
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=132808
Your paranoid android.
=== debian11 (32 bit report) ===
winhttp: notification.c:118: Test failed: 994: expected status 0x20000 got 0x4000 notification.c:118: Test failed: 994: expected status 0x20000 got 0x1 notification.c:123: Test failed: 994: expected callback 0x1 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x2 notification.c:123: Test failed: 994: expected callback 0x2 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x4 notification.c:123: Test failed: 994: expected callback 0x4 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x8 notification.c:123: Test failed: 994: expected callback 0x8 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x10 notification.c:123: Test failed: 994: expected callback 0x10 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x20 notification.c:123: Test failed: 994: expected callback 0x20 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x40 notification.c:123: Test failed: 994: expected callback 0x40 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x80 notification.c:123: Test failed: 994: expected callback 0x80 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x4000 notification.c:123: Test failed: 994: expected callback 0x4000 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x10 notification.c:123: Test failed: 994: expected callback 0x10 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x20 notification.c:123: Test failed: 994: expected callback 0x20 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x40 notification.c:123: Test failed: 994: expected callback 0x40 to be called from the same thread notification.c:118: Test failed: 994: expected status 0x20000 got 0x80 notification.c:123: Test failed: 994: expected callback 0x80 to be called from the same thread notification.c:123: Test failed: 994: expected callback 0x20000 to be called from the same thread notification.c:1174: Test failed: got 400 notification.c:118: Test failed: 1181: expected status 0x80000 got 0x200000 notification: Timeout winhttp.c:3543: Test failed: got 8 winhttp.c:3549: Test failed: got 12019 winhttp.c:3550: Test failed: got 3735928559 Unhandled exception: page fault on write access to 0x00000008 in 32-bit code (0x63c04786).