Merge branch 'dbus-notifications' into 'master'

org.freedesktop.Notifications implementation for balloons

See merge request wine/wine!6200
This commit is contained in:
Sergei Chernyadyev 2024-11-19 22:27:39 +00:00
commit 457192f0a5
10 changed files with 1060 additions and 3 deletions

View file

@ -3,8 +3,8 @@ MODULE = win32u.dll
UNIXLIB = win32u.so
IMPORTLIB = win32u
IMPORTS = ntdll winecrt0
UNIX_CFLAGS = $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS)
UNIX_LIBS = $(CARBON_LIBS) $(APPKIT_LIBS) $(PTHREAD_LIBS) -lm
UNIX_CFLAGS = $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) $(DBUS_CFLAGS)
UNIX_LIBS = $(CARBON_LIBS) $(APPKIT_LIBS) $(PTHREAD_LIBS) $(DBUS_LIBS) -lm
EXTRADLLFLAGS = -nodefaultlibs
@ -48,6 +48,8 @@ SOURCES = \
rawinput.c \
region.c \
scroll.c \
snidrv/image.c \
snidrv/dbus.c \
spy.c \
syscall.c \
sysparams.c \

View file

@ -746,6 +746,11 @@ static BOOL nulldrv_SystrayDockRemove( HWND hwnd )
return FALSE;
}
static BOOL nulldrv_SystrayShowBalloon( HWND hwnd, UINT uID, BOOL hidden, struct systray_balloon *icon )
{
return FALSE;
}
static void nulldrv_UpdateClipboard(void)
{
}
@ -1156,6 +1161,11 @@ static BOOL loaderdrv_SystrayDockRemove( HWND hwnd )
return load_driver()->pSystrayDockRemove( hwnd );
}
static BOOL loaderdrv_SystrayShowBalloon( HWND hwnd, UINT uID, BOOL hidden, struct systray_balloon *icon )
{
return load_driver()->pSystrayShowBalloon( hwnd, uID, hidden, icon );
}
static LRESULT nulldrv_ClipboardWindowProc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
{
return 0;
@ -1248,6 +1258,7 @@ static const struct user_driver_funcs lazy_load_driver =
loaderdrv_SystrayDockInsert,
loaderdrv_SystrayDockClear,
loaderdrv_SystrayDockRemove,
loaderdrv_SystrayShowBalloon,
/* clipboard functions */
nulldrv_ClipboardWindowProc,
loaderdrv_UpdateClipboard,
@ -1339,6 +1350,7 @@ void __wine_set_user_driver( const struct user_driver_funcs *funcs, UINT version
SET_USER_FUNC(SystrayDockInsert);
SET_USER_FUNC(SystrayDockClear);
SET_USER_FUNC(SystrayDockRemove);
SET_USER_FUNC(SystrayShowBalloon);
SET_USER_FUNC(ClipboardWindowProc);
SET_USER_FUNC(UpdateClipboard);
SET_USER_FUNC(ChangeDisplaySettings);

755
dlls/win32u/snidrv/dbus.c Normal file
View file

@ -0,0 +1,755 @@
/*
* DBus tray support
*
* Copyright 2023 Sergei Chernyadyev
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#if 0
#pragma makedep unix
#endif
#include "config.h"
#ifdef SONAME_LIBDBUS_1
#include "snidrv.h"
#include <pthread.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <limits.h>
#include <dlfcn.h>
#include <poll.h>
#include <dbus/dbus.h>
#include "wine/list.h"
#include "wine/unixlib.h"
#include "wine/gdi_driver.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(winesni);
#define DBUS_FUNCS \
DO_FUNC(dbus_bus_add_match); \
DO_FUNC(dbus_bus_get); \
DO_FUNC(dbus_bus_get_private); \
DO_FUNC(dbus_bus_add_match); \
DO_FUNC(dbus_bus_remove_match); \
DO_FUNC(dbus_bus_get_unique_name); \
DO_FUNC(dbus_connection_add_filter); \
DO_FUNC(dbus_connection_read_write); \
DO_FUNC(dbus_connection_dispatch); \
DO_FUNC(dbus_connection_get_dispatch_status); \
DO_FUNC(dbus_connection_read_write_dispatch); \
DO_FUNC(dbus_connection_remove_filter); \
DO_FUNC(dbus_connection_send); \
DO_FUNC(dbus_connection_send_with_reply); \
DO_FUNC(dbus_connection_send_with_reply_and_block); \
DO_FUNC(dbus_connection_flush); \
DO_FUNC(dbus_connection_try_register_object_path); \
DO_FUNC(dbus_connection_unregister_object_path); \
DO_FUNC(dbus_connection_list_registered); \
DO_FUNC(dbus_connection_close); \
DO_FUNC(dbus_connection_ref); \
DO_FUNC(dbus_connection_unref); \
DO_FUNC(dbus_connection_get_object_path_data); \
DO_FUNC(dbus_connection_set_watch_functions); \
DO_FUNC(dbus_watch_get_unix_fd); \
DO_FUNC(dbus_watch_handle); \
DO_FUNC(dbus_watch_get_flags); \
DO_FUNC(dbus_watch_get_enabled); \
DO_FUNC(dbus_error_free); \
DO_FUNC(dbus_error_init); \
DO_FUNC(dbus_error_is_set); \
DO_FUNC(dbus_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
static pthread_once_t init_control = PTHREAD_ONCE_INIT;
struct standalone_notification {
struct list entry;
HWND owner;
UINT id;
unsigned int notification_id;
};
static struct list standalone_notification_list = LIST_INIT( standalone_notification_list );
static pthread_mutex_t standalone_notifications_mutex = PTHREAD_MUTEX_INITIALIZER;
#define BALLOON_SHOW_MIN_TIMEOUT 10000
#define BALLOON_SHOW_MAX_TIMEOUT 30000
static const char* notifications_interface_name = "org.freedesktop.Notifications";
static void* dbus_module = NULL;
static DBusConnection *global_connection;
static DBusWatch *global_connection_watch;
static int global_connection_watch_fd;
static UINT global_connection_watch_flags;
static BOOL sni_initialized = FALSE;
static BOOL notifications_initialized = FALSE;
static char* notifications_dst_path = NULL;
static BOOL load_dbus_functions(void)
{
if (!(dbus_module = dlopen( SONAME_LIBDBUS_1, RTLD_NOW )))
goto failed;
#define DO_FUNC(f) if (!(p_##f = dlsym( dbus_module, #f ))) goto failed
DBUS_FUNCS;
#undef DO_FUNC
return TRUE;
failed:
WARN( "failed to load DBUS support: %s\n", dlerror() );
return FALSE;
}
static void notifications_finalize(void)
{
free(notifications_dst_path);
}
static void dbus_finalize(void)
{
if (global_connection != NULL)
{
p_dbus_connection_flush(global_connection);
p_dbus_connection_close(global_connection);
p_dbus_connection_unref(global_connection);
}
if (dbus_module != NULL)
{
dlclose(dbus_module);
}
}
static dbus_bool_t add_watch(DBusWatch *w, void *data);
static void remove_watch(DBusWatch *w, void *data);
static void toggle_watch(DBusWatch *w, void *data);
static BOOL notifications_initialize(void);
static BOOL dbus_initialize(void)
{
DBusError error;
p_dbus_error_init( &error );
if (!p_dbus_threads_init_default()) return FALSE;
if (!(global_connection = p_dbus_bus_get_private( DBUS_BUS_SESSION, &error )))
{
WARN("failed to get system dbus connection: %s\n", error.message );
p_dbus_error_free( &error );
return FALSE;
}
if (!p_dbus_connection_set_watch_functions(global_connection, add_watch, remove_watch,
toggle_watch, NULL, NULL))
{
WARN("dbus_set_watch_functions() failed\n");
return FALSE;
}
return TRUE;
}
static void snidrv_once_initialize(void)
{
if (!load_dbus_functions()) goto err;
if (!dbus_initialize()) goto err;
if (notifications_initialize())
notifications_initialized = TRUE;
sni_initialized = TRUE;
err:
if (!notifications_initialized)
notifications_finalize();
if (!sni_initialized && !notifications_initialized)
dbus_finalize();
}
BOOL snidrv_notification_init(void)
{
pthread_once(&init_control, snidrv_once_initialize);
return notifications_initialized;
}
static dbus_bool_t add_watch(DBusWatch *w, void *data)
{
int fd;
unsigned int flags, poll_flags;
if (!p_dbus_watch_get_enabled(w))
return TRUE;
fd = p_dbus_watch_get_unix_fd(w);
flags = p_dbus_watch_get_flags(w);
poll_flags = 0;
if (flags & DBUS_WATCH_READABLE)
poll_flags |= POLLIN;
if (flags & DBUS_WATCH_WRITABLE)
poll_flags |= POLLOUT;
/* global connection */
global_connection_watch_fd = fd;
global_connection_watch_flags = poll_flags;
global_connection_watch = w;
return TRUE;
}
static void remove_watch(DBusWatch *w, void *data)
{
/* global connection */
global_connection_watch_fd = 0;
global_connection_watch_flags = 0;
global_connection_watch = NULL;
}
static void toggle_watch(DBusWatch *w, void *data)
{
if (p_dbus_watch_get_enabled(w))
add_watch(w, data);
else
remove_watch(w, data);
}
static const char* dbus_name_owning_match = "type='signal',"
"interface='org.freedesktop.DBus',"
"sender='org.freedesktop.DBus',"
"member='NameOwnerChanged'";
static const char* dbus_notification_close_signal = "type='signal',"
"interface='org.freedesktop.Notifications',"
"member='NotificationClosed'";
static DBusHandlerResult name_owner_filter( DBusConnection *ctx, DBusMessage *msg, void *user_data )
{
char *interface_name, *old_path, *new_path;
DBusError error;
p_dbus_error_init( &error );
if (p_dbus_message_is_signal( msg, "org.freedesktop.DBus", "NameOwnerChanged" ) &&
p_dbus_message_get_args( msg, &error, DBUS_TYPE_STRING, &interface_name, DBUS_TYPE_STRING, &old_path,
DBUS_TYPE_STRING, &new_path, DBUS_TYPE_INVALID ))
{
if (strcmp(interface_name, notifications_interface_name) == 0)
{
struct standalone_notification *this, *next;
pthread_mutex_lock(&standalone_notifications_mutex);
old_path = notifications_dst_path;
notifications_dst_path = strdup(new_path);
free(old_path);
LIST_FOR_EACH_ENTRY_SAFE( this, next, &standalone_notification_list, struct standalone_notification, entry )
{
list_remove(&this->entry);
free(this);
}
pthread_mutex_unlock(&standalone_notifications_mutex);
}
}
else if (p_dbus_message_is_signal( msg, notifications_interface_name, "NotificationClosed" ))
{
unsigned int id, reason;
struct standalone_notification *this, *next;
if (!p_dbus_message_get_args( msg, &error, DBUS_TYPE_UINT32, &id, DBUS_TYPE_UINT32, &reason ))
goto cleanup;
pthread_mutex_lock(&standalone_notifications_mutex);
/* TODO: clear the list */
LIST_FOR_EACH_ENTRY_SAFE( this, next, &standalone_notification_list, struct standalone_notification, entry )
{
if (this->notification_id == id)
{
list_remove(&this->entry);
free(this);
}
}
pthread_mutex_unlock(&standalone_notifications_mutex);
}
cleanup:
p_dbus_error_free( &error );
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static BOOL get_owner_for_interface(DBusConnection* connection, const char* interface_name, char** owner_path)
{
DBusMessage* msg = NULL;
DBusMessageIter args;
DBusPendingCall* pending;
DBusError error;
char* status_notifier_dest = NULL;
p_dbus_error_init( &error );
msg = p_dbus_message_new_method_call("org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"GetNameOwner");
if (!msg) goto err;
p_dbus_message_iter_init_append(msg, &args);
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &interface_name )) goto err;
if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1)) goto err;
if (!pending) goto err;
p_dbus_message_unref(msg);
p_dbus_pending_call_block(pending);
msg = p_dbus_pending_call_steal_reply(pending);
p_dbus_pending_call_unref(pending);
if (!msg) goto err;
if (p_dbus_set_error_from_message (&error, msg))
{
WARN("failed to query an owner - %s: %s\n", error.name, error.message);
p_dbus_error_free( &error);
goto err;
}
else if (!p_dbus_message_get_args( msg, &error, DBUS_TYPE_STRING, &status_notifier_dest,
DBUS_TYPE_INVALID ))
{
WARN("failed to get a response - %s: %s\n", error.name, error.message);
p_dbus_error_free( &error );
goto err;
}
*owner_path = strdup(status_notifier_dest);
p_dbus_message_unref(msg);
return TRUE;
err:
p_dbus_message_unref(msg);
return FALSE;
}
static BOOL notifications_initialize(void)
{
DBusError error;
p_dbus_error_init( &error );
if (!get_owner_for_interface(global_connection, "org.freedesktop.Notifications", &notifications_dst_path))
{
goto err;
}
p_dbus_connection_add_filter( global_connection, name_owner_filter, NULL, NULL );
p_dbus_bus_add_match( global_connection, dbus_name_owning_match, &error );
p_dbus_bus_add_match( global_connection, dbus_notification_close_signal, &error );
if (p_dbus_error_is_set(&error))
{
WARN("failed to register matcher %s: %s\n", error.name, error.message);
p_dbus_error_free( &error);
goto err;
}
return TRUE;
err:
return FALSE;
}
static BOOL handle_notification_icon(DBusMessageIter *iter, const unsigned char* icon_bits, unsigned width, unsigned height)
{
DBusMessageIter sIter,bIter;
unsigned row_stride = width * 4;
const unsigned channel_count = 4;
const unsigned bits_per_sample = 8;
const bool has_alpha = true;
if (!p_dbus_message_iter_open_container(iter, 'r', NULL, &sIter))
{
WARN("Failed to open struct inside array!\n");
goto fail;
}
p_dbus_message_iter_append_basic(&sIter, 'i', &width);
p_dbus_message_iter_append_basic(&sIter, 'i', &height);
p_dbus_message_iter_append_basic(&sIter, 'i', &row_stride);
p_dbus_message_iter_append_basic(&sIter, 'b', &has_alpha);
p_dbus_message_iter_append_basic(&sIter, 'i', &bits_per_sample);
p_dbus_message_iter_append_basic(&sIter, 'i', &channel_count);
if (p_dbus_message_iter_open_container(&sIter, 'a', DBUS_TYPE_BYTE_AS_STRING, &bIter))
{
p_dbus_message_iter_append_fixed_array(&bIter, DBUS_TYPE_BYTE, &icon_bits, width * height * 4);
p_dbus_message_iter_close_container(&sIter, &bIter);
}
else
{
p_dbus_message_iter_abandon_container_if_open(iter, &sIter);
goto fail;
}
p_dbus_message_iter_close_container(iter, &sIter);
return TRUE;
fail:
return FALSE;
}
static BOOL close_notification(DBusConnection* connection, UINT id)
{
BOOL ret = FALSE;
DBusMessage* msg = NULL;
DBusMessageIter args;
DBusPendingCall* pending;
DBusError error;
p_dbus_error_init( &error );
msg = p_dbus_message_new_method_call(notifications_dst_path,
"/org/freedesktop/Notifications",
notifications_interface_name,
"CloseNotification");
if (!msg) goto err;
p_dbus_message_iter_init_append(msg, &args);
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &id )) goto err;
if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1))
goto err;
if (!pending) goto err;
p_dbus_message_unref(msg);
p_dbus_pending_call_block(pending);
msg = p_dbus_pending_call_steal_reply(pending);
p_dbus_pending_call_unref(pending);
if (!msg) goto err;
if (p_dbus_set_error_from_message (&error, msg))
{
WARN("got an error - %s: %s\n", error.name, error.message);
p_dbus_error_free( &error);
}
ret = TRUE;
err:
p_dbus_message_unref(msg);
return ret;
}
static BOOL send_notification(DBusConnection* connection, UINT id, const WCHAR* title, const WCHAR* text, HICON icon, UINT info_flags, UINT timeout, unsigned int *p_new_id)
{
char info_text[256 * 3];
char info_title[128 * 3];
const char *info_text_ptr = info_text, *info_title_ptr = info_title;
const char* empty_string = "";
const char* icon_name = "";
BOOL ret = FALSE;
DBusMessage* msg = NULL;
DBusMessageIter args, aIter, eIter, vIter;
DBusPendingCall* pending;
DBusError error;
/* icon */
void* icon_bits = NULL;
unsigned width, height;
HICON new_icon = NULL;
int expire_timeout;
/* no text for balloon, so no balloon */
if (!text || !text[0])
return TRUE;
info_title[0] = 0;
info_text[0] = 0;
if (title) ntdll_wcstoumbs(title, wcslen(title) + 1, info_title, ARRAY_SIZE(info_title), FALSE);
if (text) ntdll_wcstoumbs(text, wcslen(text) + 1, info_text, ARRAY_SIZE(info_text), FALSE);
/*icon*/
if ((info_flags & NIIF_ICONMASK) == NIIF_USER && icon)
{
unsigned int *u_icon_bits;
new_icon = CopyImage(icon, IMAGE_ICON, 0, 0, 0);
if (!create_bitmap_from_icon(new_icon, &width, &height, &icon_bits))
{
WARN("failed to copy icon %p\n", new_icon);
goto err;
}
u_icon_bits = icon_bits;
/* convert to RGBA, turns out that unlike tray icons it needs RGBA */
for (unsigned i = 0; i < width * height; i++)
{
#ifdef WORDS_BIGENDIAN
u_icon_bits[i] = (u_icon_bits[i] << 8) | (u_icon_bits[i] >> 24);
#else
u_icon_bits[i] = (u_icon_bits[i] << 24) | (u_icon_bits[i] >> 8);
#endif
}
}
else
{
/* show placeholder icons */
switch (info_flags & NIIF_ICONMASK)
{
case NIIF_INFO:
icon_name = "dialog-information";
break;
case NIIF_WARNING:
icon_name = "dialog-warning";
break;
case NIIF_ERROR:
icon_name = "dialog-error";
break;
default:
break;
}
}
p_dbus_error_init( &error );
msg = p_dbus_message_new_method_call(notifications_dst_path,
"/org/freedesktop/Notifications",
notifications_interface_name,
"Notify");
if (!msg) goto err;
p_dbus_message_iter_init_append(msg, &args);
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &empty_string ))
goto err;
/* override id */
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &id ))
goto err;
/* icon name */
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &icon_name ))
goto err;
/* title */
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info_title_ptr ))
goto err;
/* body */
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info_text_ptr ))
goto err;
/* actions */
/* TODO: add default action */
if (p_dbus_message_iter_open_container(&args, 'a', DBUS_TYPE_STRING_AS_STRING, &aIter))
p_dbus_message_iter_close_container(&args, &aIter);
else
goto err;
/* hints */
if (p_dbus_message_iter_open_container(&args, 'a', "{sv}", &aIter))
{
if ((info_flags & NIIF_ICONMASK) == NIIF_USER && icon)
{
const char* icon_data_field = "image-data";
if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter))
{
p_dbus_message_iter_abandon_container_if_open(&args, &aIter);
goto err;
}
p_dbus_message_iter_append_basic(&eIter, 's', &icon_data_field);
if (!p_dbus_message_iter_open_container(&eIter, 'v', "(iiibiiay)", &vIter))
{
p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter);
p_dbus_message_iter_abandon_container_if_open(&args, &aIter);
goto err;
}
if (!handle_notification_icon(&vIter, icon_bits, width, height))
{
p_dbus_message_iter_abandon_container_if_open(&eIter, &vIter);
p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter);
p_dbus_message_iter_abandon_container_if_open(&args, &aIter);
goto err;
}
p_dbus_message_iter_close_container(&eIter, &vIter);
p_dbus_message_iter_close_container(&aIter, &eIter);
}
p_dbus_message_iter_close_container(&args, &aIter);
}
else
goto err;
if (timeout == 0)
/* just set it to system default */
expire_timeout = -1;
else
expire_timeout = max(min(timeout, BALLOON_SHOW_MAX_TIMEOUT), BALLOON_SHOW_MIN_TIMEOUT);
/* timeout */
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, &expire_timeout ))
goto err;
if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1))
goto err;
if (!pending) goto err;
p_dbus_message_unref(msg);
p_dbus_pending_call_block(pending);
msg = p_dbus_pending_call_steal_reply(pending);
p_dbus_pending_call_unref(pending);
if (!msg) goto err;
if (p_dbus_set_error_from_message (&error, msg))
{
WARN("failed to create a notification - %s: %s\n", error.name, error.message);
p_dbus_error_free( &error);
goto err;
}
if (!p_dbus_message_iter_init(msg, &args))
goto err;
if (DBUS_TYPE_UINT32 != p_dbus_message_iter_get_arg_type(&args))
goto err;
else if (p_new_id)
p_dbus_message_iter_get_basic(&args, p_new_id);
ret = TRUE;
err:
p_dbus_message_unref(msg);
if (new_icon) NtUserDestroyCursor(new_icon, 0);
free(icon_bits);
return ret;
}
BOOL snidrv_run_loop()
{
while (true)
{
int poll_ret;
struct pollfd fd_info;
DBusConnection* conn;
/* TODO: add condvar */
if (!global_connection_watch_fd) continue;
conn = p_dbus_connection_ref(global_connection);
fd_info = (struct pollfd) {
.fd = global_connection_watch_fd,
.events = global_connection_watch_flags,
.revents = 0,
};
poll_ret = poll(&fd_info, 1, 100);
if (poll_ret == 0)
goto cleanup;
if (poll_ret == -1)
{
ERR("fd poll error\n");
goto cleanup;
}
if (fd_info.revents & (POLLERR | POLLHUP | POLLNVAL)) continue;
if (fd_info.revents & POLLIN)
{
p_dbus_watch_handle(global_connection_watch, DBUS_WATCH_READABLE);
while ( p_dbus_connection_get_dispatch_status ( conn ) == DBUS_DISPATCH_DATA_REMAINS )
{
p_dbus_connection_dispatch ( conn ) ;
}
}
if (fd_info.revents & POLLOUT)
p_dbus_watch_handle(global_connection_watch, DBUS_WATCH_WRITABLE);
cleanup:
p_dbus_connection_unref(conn);
}
return 0;
}
BOOL snidrv_show_balloon( HWND owner, UINT id, BOOL hidden, const struct systray_balloon* balloon )
{
BOOL ret = TRUE;
struct standalone_notification *found_notification = NULL, *this;
if (!notifications_dst_path || !notifications_dst_path[0])
return -1;
pthread_mutex_lock(&standalone_notifications_mutex);
LIST_FOR_EACH_ENTRY(this, &standalone_notification_list, struct standalone_notification, entry)
{
if (this->owner == owner && this->id == id)
{
found_notification = this;
break;
}
}
/* close existing notification anyway */
if (!hidden)
{
if (!found_notification)
{
found_notification = malloc(sizeof(struct standalone_notification));
if (!found_notification)
{
ret = FALSE;
goto cleanup;
}
found_notification->owner = owner;
found_notification->id = id;
found_notification->notification_id = 0;
list_add_tail(&standalone_notification_list, &found_notification->entry);
}
else
TRACE("found existing notification %p %d\n", owner, id);
ret = send_notification(global_connection,
found_notification->notification_id,
balloon->info_title,
balloon->info_text,
balloon->info_icon,
balloon->info_flags,
balloon->info_timeout,
&found_notification->notification_id);
}
else if (found_notification)
{
ret = close_notification(global_connection, found_notification->notification_id);
}
cleanup:
pthread_mutex_unlock(&standalone_notifications_mutex);
return ret;
}
#endif

169
dlls/win32u/snidrv/image.c Normal file
View file

@ -0,0 +1,169 @@
/*
* DBus tray support
*
* Copyright 2023 Sergei Chernyadyev
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#if 0
#pragma makedep unix
#endif
#include "config.h"
#ifdef SONAME_LIBDBUS_1
#include "snidrv.h"
#include "stdlib.h"
#include "ntuser.h"
#include "ntgdi.h"
/***********************************************************************
* get_mono_icon_argb
*
* Return a monochrome icon/cursor bitmap bits in ARGB format.
*/
static unsigned int *get_mono_icon_argb( HDC hdc, HBITMAP bmp, unsigned int *width, unsigned int *height )
{
BITMAP bm;
char *mask;
unsigned int i, j, stride, mask_size, bits_size, *bits = NULL, *ptr;
if (!NtGdiExtGetObjectW( bmp, sizeof(bm), &bm )) return NULL;
stride = ((bm.bmWidth + 15) >> 3) & ~1;
mask_size = stride * bm.bmHeight;
if (!(mask = malloc( mask_size ))) return NULL;
if (!NtGdiGetBitmapBits( bmp, mask_size, mask )) goto done;
bm.bmHeight /= 2;
bits_size = bm.bmWidth * bm.bmHeight * sizeof(*bits);
if (!(bits = malloc( bits_size ))) goto done;
ptr = bits;
for (i = 0; i < bm.bmHeight; i++)
for (j = 0; j < bm.bmWidth; j++, ptr++)
{
int and = ((mask[i * stride + j / 8] << (j % 8)) & 0x80);
int xor = ((mask[(i + bm.bmHeight) * stride + j / 8] << (j % 8)) & 0x80);
if (!xor && and)
*ptr = 0;
else if (xor && !and)
*ptr = 0xffffffff;
else
/* we can't draw "invert" pixels, so render them as black instead */
#ifdef WORDS_BIGENDIAN
*ptr = 0xff000000;
#else
*ptr = 0x000000ff;
#endif
}
*width = bm.bmWidth;
*height = bm.bmHeight;
done:
free( mask );
return bits;
}
/***********************************************************************
* get_bitmap_argb
*
* Return the bitmap bits in ARGB format. Helper for setting icons and cursors.
*/
static unsigned int *get_bitmap_argb( HDC hdc, HBITMAP color, HBITMAP mask, unsigned int *width,
unsigned int *height )
{
char buffer[FIELD_OFFSET( BITMAPINFO, bmiColors[256] )];
BITMAPINFO *info = (BITMAPINFO *)buffer;
BITMAP bm;
unsigned int *ptr, *bits = NULL;
unsigned char *mask_bits = NULL;
int i, j;
BOOL has_alpha = FALSE;
if (!color) return get_mono_icon_argb( hdc, mask, width, height );
if (!NtGdiExtGetObjectW( color, sizeof(bm), &bm )) return NULL;
info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info->bmiHeader.biWidth = bm.bmWidth;
info->bmiHeader.biHeight = -bm.bmHeight;
info->bmiHeader.biPlanes = 1;
info->bmiHeader.biBitCount = 32;
info->bmiHeader.biCompression = BI_RGB;
info->bmiHeader.biSizeImage = bm.bmWidth * bm.bmHeight * 4;
info->bmiHeader.biXPelsPerMeter = 0;
info->bmiHeader.biYPelsPerMeter = 0;
info->bmiHeader.biClrUsed = 0;
info->bmiHeader.biClrImportant = 0;
if (!(bits = malloc( bm.bmWidth * bm.bmHeight * sizeof(unsigned int) )))
goto failed;
if (!NtGdiGetDIBitsInternal( hdc, color, 0, bm.bmHeight, bits, info, DIB_RGB_COLORS, 0, 0 ))
goto failed;
*width = bm.bmWidth;
*height = bm.bmHeight;
for (i = 0; i < bm.bmWidth * bm.bmHeight; i++)
if ((has_alpha = (bits[i] & 0xff000000) != 0)) break;
if (!has_alpha)
{
unsigned int width_bytes = (bm.bmWidth + 31) / 32 * 4;
/* generate alpha channel from the mask */
info->bmiHeader.biBitCount = 1;
info->bmiHeader.biSizeImage = width_bytes * bm.bmHeight;
if (!(mask_bits = malloc( info->bmiHeader.biSizeImage ))) goto failed;
if (!NtGdiGetDIBitsInternal( hdc, mask, 0, bm.bmHeight, mask_bits, info, DIB_RGB_COLORS, 0, 0 ))
goto failed;
ptr = bits;
for (i = 0; i < bm.bmHeight; i++)
for (j = 0; j < bm.bmWidth; j++, ptr++)
if (!((mask_bits[i * width_bytes + j / 8] << (j % 8)) & 0x80)) *ptr |= 0xff000000;
free( mask_bits );
}
#ifndef WORDS_BIGENDIAN
for (unsigned i = 0; i < bm.bmWidth * bm.bmHeight; i++)
{
bits[i] = ((bits[i] & 0xFF) << 24) | ((bits[i] & 0xFF00) << 8) | ((bits[i] & 0xFF0000) >> 8) | ((bits[i] & 0xFF000000) >> 24);
}
#endif
return bits;
failed:
free( bits );
free( mask_bits );
*width = *height = 0;
return NULL;
}
BOOL create_bitmap_from_icon(HANDLE icon, unsigned *p_width, unsigned *p_height, void** p_bits)
{
ICONINFO info;
HDC hdc;
if (!NtUserGetIconInfo(icon, &info, NULL, NULL, NULL, 0))
return FALSE;
hdc = NtGdiCreateCompatibleDC( 0 );
*p_bits = get_bitmap_argb( hdc, info.hbmColor, info.hbmMask, p_width, p_height );
NtGdiDeleteObjectApp( info.hbmMask );
NtGdiDeleteObjectApp( hdc );
return *p_bits != NULL;
}
#endif

View file

@ -0,0 +1,40 @@
/*
* SNI driver include file
*
* Copyright 2023 Sergei Chernyadyev
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef __WINE_SNIDRV_H
#define __WINE_SNIDRV_H
#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "shellapi.h"
#include "ntuser.h"
/* snidrv */
extern BOOL snidrv_notification_init(void);
extern BOOL snidrv_run_loop(void);
extern BOOL create_bitmap_from_icon(HANDLE icon, unsigned *p_width, unsigned *p_height, void** p_bits);
extern BOOL snidrv_show_balloon( HWND owner, UINT id, BOOL hidden, const struct systray_balloon* balloon );
#endif

View file

@ -26,13 +26,37 @@
#define WIN32_NO_STATUS
#include "win32u_private.h"
#include "ntuser_private.h"
#ifdef SONAME_LIBDBUS_1
#include "snidrv/snidrv.h"
#endif
#include "shellapi.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(systray);
#ifdef SONAME_LIBDBUS_1
static volatile LONG dbus_notifications_initialized = (LONG)FALSE;
#endif
LRESULT system_tray_call( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, void *data )
{
#ifdef SONAME_LIBDBUS_1
LONG l_dbus_notifications_initialized = InterlockedCompareExchange(&dbus_notifications_initialized, (LONG)FALSE, (LONG)FALSE);
if (!l_dbus_notifications_initialized && snidrv_notification_init())
{
InterlockedCompareExchange(&dbus_notifications_initialized, TRUE, FALSE);
l_dbus_notifications_initialized = TRUE;
}
if (l_dbus_notifications_initialized)
{
if (msg == WINE_SYSTRAY_RUN_LOOP)
return snidrv_run_loop();
if (msg == WINE_SYSTRAY_SHOW_BALLOON)
return snidrv_show_balloon(hwnd, wparam, lparam, data);
}
#endif
switch (msg)
{
case WINE_SYSTRAY_NOTIFY_ICON:
@ -52,6 +76,12 @@ LRESULT system_tray_call( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, voi
case WINE_SYSTRAY_DOCK_REMOVE:
return user_driver->pSystrayDockRemove( hwnd );
case WINE_SYSTRAY_RUN_LOOP:
return -1;
case WINE_SYSTRAY_SHOW_BALLOON:
return user_driver->pSystrayShowBalloon( hwnd, wparam, lparam, data );
default:
FIXME( "Unknown NtUserSystemTrayCall msg %#x\n", msg );
break;

View file

@ -3613,6 +3613,25 @@ NTSTATUS WINAPI wow64_NtUserMessageCall( UINT *args )
case NtUserSystemTrayCall:
switch (msg)
{
case WINE_SYSTRAY_SHOW_BALLOON:
{
struct
{
WCHAR info_text[256]; /* info balloon text */
WCHAR info_title[64]; /* info balloon title */
UINT info_flags; /* flags for info balloon */
UINT info_timeout; /* timeout for info balloon */
ULONG info_icon; /* info balloon icon */
} *balloon_params32 = result_info;
struct systray_balloon balloon_params;
balloon_params.info_flags = balloon_params32->info_flags;
balloon_params.info_timeout = balloon_params32->info_timeout;
balloon_params.info_icon = UlongToHandle(balloon_params32->info_icon);
wcscpy( balloon_params.info_text, balloon_params32->info_text );
wcscpy( balloon_params.info_title, balloon_params32->info_title );
return NtUserMessageCall( hwnd, msg, wparam, lparam, &balloon_params, type, ansi );
}
case WINE_SYSTRAY_NOTIFY_ICON:
{
struct

View file

@ -656,6 +656,17 @@ enum wine_systray_call
WINE_SYSTRAY_DOCK_INSERT,
WINE_SYSTRAY_DOCK_CLEAR,
WINE_SYSTRAY_DOCK_REMOVE,
WINE_SYSTRAY_RUN_LOOP,
WINE_SYSTRAY_SHOW_BALLOON,
};
struct systray_balloon
{
WCHAR info_text[256]; /* info balloon text */
WCHAR info_title[64]; /* info balloon title */
UINT info_flags; /* flags for info balloon */
UINT info_timeout; /* timeout for info balloon */
HICON info_icon; /* info balloon icon */
};
/* NtUserDragDropCall calls */

View file

@ -354,6 +354,7 @@ struct user_driver_funcs
BOOL (*pSystrayDockInsert)(HWND,UINT,UINT,void *);
void (*pSystrayDockClear)(HWND);
BOOL (*pSystrayDockRemove)(HWND);
BOOL (*pSystrayShowBalloon)(HWND,UINT,BOOL,struct systray_balloon *);
/* clipboard functions */
LRESULT (*pClipboardWindowProc)(HWND,UINT,WPARAM,LPARAM);
void (*pUpdateClipboard)(void);

View file

@ -302,7 +302,18 @@ static void show_next_balloon(void)
static void update_balloon( struct icon *icon )
{
if (balloon_icon == icon)
struct systray_balloon balloon_info;
balloon_info.info_flags = icon->info_flags;
balloon_info.info_timeout = icon->info_timeout;
balloon_info.info_icon = icon->info_icon;
wcscpy( balloon_info.info_text, icon->info_text );
wcscpy( balloon_info.info_title, icon->info_title );
if (NtUserMessageCall( icon->window, WINE_SYSTRAY_SHOW_BALLOON, icon->id, icon->display == ICON_DISPLAY_HIDDEN,
&balloon_info, NtUserSystemTrayCall, FALSE ) > 0)
{
return;
}
else if (balloon_icon == icon)
{
hide_balloon( icon );
show_balloon( icon );
@ -1188,6 +1199,11 @@ void handle_parent_notify( HWND hwnd, WPARAM wp )
sync_taskbar_buttons();
}
static DWORD WINAPI systray_run_loop(void* arg)
{
return NtUserMessageCall( tray_window, WINE_SYSTRAY_RUN_LOOP, 0, 0, NULL, NtUserSystemTrayCall, FALSE ) == 0;
}
/* this function creates the listener window */
void initialize_systray( BOOL arg_using_root, BOOL arg_enable_shell, BOOL arg_show_systray, BOOL arg_no_tray_items )
{
@ -1244,6 +1260,8 @@ void initialize_systray( BOOL arg_using_root, BOOL arg_enable_shell, BOOL arg_sh
tray_window = CreateWindowExW( 0, shell_traywnd_class.lpszClassName, L"", WS_CAPTION | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, size.cx, size.cy, 0, 0, 0, 0 );
NtUserMessageCall( tray_window, WINE_SYSTRAY_DOCK_INIT, 0, 0, NULL, NtUserSystemTrayCall, FALSE );
/* run loop if SNI is being used */
CloseHandle(CreateThread(NULL, 0, systray_run_loop, NULL, 0, NULL));
}
if (!tray_window)