mirror of
https://gitlab.winehq.org/wine/wine.git
synced 2024-11-21 17:09:06 -07:00
Merge branch 'dbus-notifications' into 'master'
org.freedesktop.Notifications implementation for balloons See merge request wine/wine!6200
This commit is contained in:
commit
457192f0a5
10 changed files with 1060 additions and 3 deletions
|
@ -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 \
|
||||
|
|
|
@ -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
755
dlls/win32u/snidrv/dbus.c
Normal 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", ¬ifications_dst_path))
|
||||
{
|
||||
goto err;
|
||||
}
|
||||
|
||||
p_dbus_connection_add_filter( global_connection, name_owner_filter, NULL, NULL );
|
||||
p_dbus_bus_add_match( global_connection, dbus_name_owning_match, &error );
|
||||
p_dbus_bus_add_match( global_connection, dbus_notification_close_signal, &error );
|
||||
|
||||
if (p_dbus_error_is_set(&error))
|
||||
{
|
||||
WARN("failed to register matcher %s: %s\n", error.name, error.message);
|
||||
p_dbus_error_free( &error);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
err:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOL handle_notification_icon(DBusMessageIter *iter, const unsigned char* icon_bits, unsigned width, unsigned height)
|
||||
{
|
||||
DBusMessageIter sIter,bIter;
|
||||
unsigned row_stride = width * 4;
|
||||
const unsigned channel_count = 4;
|
||||
const unsigned bits_per_sample = 8;
|
||||
const bool has_alpha = true;
|
||||
if (!p_dbus_message_iter_open_container(iter, 'r', NULL, &sIter))
|
||||
{
|
||||
WARN("Failed to open struct inside array!\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
p_dbus_message_iter_append_basic(&sIter, 'i', &width);
|
||||
p_dbus_message_iter_append_basic(&sIter, 'i', &height);
|
||||
p_dbus_message_iter_append_basic(&sIter, 'i', &row_stride);
|
||||
p_dbus_message_iter_append_basic(&sIter, 'b', &has_alpha);
|
||||
p_dbus_message_iter_append_basic(&sIter, 'i', &bits_per_sample);
|
||||
p_dbus_message_iter_append_basic(&sIter, 'i', &channel_count);
|
||||
|
||||
if (p_dbus_message_iter_open_container(&sIter, 'a', DBUS_TYPE_BYTE_AS_STRING, &bIter))
|
||||
{
|
||||
p_dbus_message_iter_append_fixed_array(&bIter, DBUS_TYPE_BYTE, &icon_bits, width * height * 4);
|
||||
p_dbus_message_iter_close_container(&sIter, &bIter);
|
||||
}
|
||||
else
|
||||
{
|
||||
p_dbus_message_iter_abandon_container_if_open(iter, &sIter);
|
||||
goto fail;
|
||||
}
|
||||
p_dbus_message_iter_close_container(iter, &sIter);
|
||||
return TRUE;
|
||||
fail:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOL close_notification(DBusConnection* connection, UINT id)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
|
||||
DBusMessage* msg = NULL;
|
||||
DBusMessageIter args;
|
||||
DBusPendingCall* pending;
|
||||
DBusError error;
|
||||
|
||||
p_dbus_error_init( &error );
|
||||
msg = p_dbus_message_new_method_call(notifications_dst_path,
|
||||
"/org/freedesktop/Notifications",
|
||||
notifications_interface_name,
|
||||
"CloseNotification");
|
||||
if (!msg) goto err;
|
||||
p_dbus_message_iter_init_append(msg, &args);
|
||||
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &id )) goto err;
|
||||
|
||||
if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1))
|
||||
goto err;
|
||||
|
||||
if (!pending) goto err;
|
||||
|
||||
p_dbus_message_unref(msg);
|
||||
|
||||
p_dbus_pending_call_block(pending);
|
||||
|
||||
msg = p_dbus_pending_call_steal_reply(pending);
|
||||
p_dbus_pending_call_unref(pending);
|
||||
if (!msg) goto err;
|
||||
if (p_dbus_set_error_from_message (&error, msg))
|
||||
{
|
||||
WARN("got an error - %s: %s\n", error.name, error.message);
|
||||
p_dbus_error_free( &error);
|
||||
}
|
||||
ret = TRUE;
|
||||
err:
|
||||
p_dbus_message_unref(msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static BOOL send_notification(DBusConnection* connection, UINT id, const WCHAR* title, const WCHAR* text, HICON icon, UINT info_flags, UINT timeout, unsigned int *p_new_id)
|
||||
{
|
||||
char info_text[256 * 3];
|
||||
char info_title[128 * 3];
|
||||
const char *info_text_ptr = info_text, *info_title_ptr = info_title;
|
||||
const char* empty_string = "";
|
||||
const char* icon_name = "";
|
||||
BOOL ret = FALSE;
|
||||
DBusMessage* msg = NULL;
|
||||
DBusMessageIter args, aIter, eIter, vIter;
|
||||
DBusPendingCall* pending;
|
||||
DBusError error;
|
||||
/* icon */
|
||||
void* icon_bits = NULL;
|
||||
unsigned width, height;
|
||||
HICON new_icon = NULL;
|
||||
int expire_timeout;
|
||||
/* no text for balloon, so no balloon */
|
||||
if (!text || !text[0])
|
||||
return TRUE;
|
||||
|
||||
info_title[0] = 0;
|
||||
info_text[0] = 0;
|
||||
if (title) ntdll_wcstoumbs(title, wcslen(title) + 1, info_title, ARRAY_SIZE(info_title), FALSE);
|
||||
if (text) ntdll_wcstoumbs(text, wcslen(text) + 1, info_text, ARRAY_SIZE(info_text), FALSE);
|
||||
/*icon*/
|
||||
if ((info_flags & NIIF_ICONMASK) == NIIF_USER && icon)
|
||||
{
|
||||
unsigned int *u_icon_bits;
|
||||
new_icon = CopyImage(icon, IMAGE_ICON, 0, 0, 0);
|
||||
if (!create_bitmap_from_icon(new_icon, &width, &height, &icon_bits))
|
||||
{
|
||||
WARN("failed to copy icon %p\n", new_icon);
|
||||
goto err;
|
||||
}
|
||||
u_icon_bits = icon_bits;
|
||||
/* convert to RGBA, turns out that unlike tray icons it needs RGBA */
|
||||
for (unsigned i = 0; i < width * height; i++)
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
u_icon_bits[i] = (u_icon_bits[i] << 8) | (u_icon_bits[i] >> 24);
|
||||
#else
|
||||
u_icon_bits[i] = (u_icon_bits[i] << 24) | (u_icon_bits[i] >> 8);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* show placeholder icons */
|
||||
switch (info_flags & NIIF_ICONMASK)
|
||||
{
|
||||
case NIIF_INFO:
|
||||
icon_name = "dialog-information";
|
||||
break;
|
||||
case NIIF_WARNING:
|
||||
icon_name = "dialog-warning";
|
||||
break;
|
||||
case NIIF_ERROR:
|
||||
icon_name = "dialog-error";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
p_dbus_error_init( &error );
|
||||
msg = p_dbus_message_new_method_call(notifications_dst_path,
|
||||
"/org/freedesktop/Notifications",
|
||||
notifications_interface_name,
|
||||
"Notify");
|
||||
if (!msg) goto err;
|
||||
|
||||
p_dbus_message_iter_init_append(msg, &args);
|
||||
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &empty_string ))
|
||||
goto err;
|
||||
/* override id */
|
||||
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &id ))
|
||||
goto err;
|
||||
/* icon name */
|
||||
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &icon_name ))
|
||||
goto err;
|
||||
/* title */
|
||||
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info_title_ptr ))
|
||||
goto err;
|
||||
/* body */
|
||||
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info_text_ptr ))
|
||||
goto err;
|
||||
/* actions */
|
||||
/* TODO: add default action */
|
||||
if (p_dbus_message_iter_open_container(&args, 'a', DBUS_TYPE_STRING_AS_STRING, &aIter))
|
||||
p_dbus_message_iter_close_container(&args, &aIter);
|
||||
else
|
||||
goto err;
|
||||
|
||||
/* hints */
|
||||
if (p_dbus_message_iter_open_container(&args, 'a', "{sv}", &aIter))
|
||||
{
|
||||
if ((info_flags & NIIF_ICONMASK) == NIIF_USER && icon)
|
||||
{
|
||||
const char* icon_data_field = "image-data";
|
||||
if (!p_dbus_message_iter_open_container(&aIter, 'e', NULL, &eIter))
|
||||
{
|
||||
p_dbus_message_iter_abandon_container_if_open(&args, &aIter);
|
||||
goto err;
|
||||
}
|
||||
|
||||
p_dbus_message_iter_append_basic(&eIter, 's', &icon_data_field);
|
||||
|
||||
if (!p_dbus_message_iter_open_container(&eIter, 'v', "(iiibiiay)", &vIter))
|
||||
{
|
||||
p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter);
|
||||
p_dbus_message_iter_abandon_container_if_open(&args, &aIter);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!handle_notification_icon(&vIter, icon_bits, width, height))
|
||||
{
|
||||
p_dbus_message_iter_abandon_container_if_open(&eIter, &vIter);
|
||||
p_dbus_message_iter_abandon_container_if_open(&aIter, &eIter);
|
||||
p_dbus_message_iter_abandon_container_if_open(&args, &aIter);
|
||||
goto err;
|
||||
}
|
||||
|
||||
p_dbus_message_iter_close_container(&eIter, &vIter);
|
||||
p_dbus_message_iter_close_container(&aIter, &eIter);
|
||||
}
|
||||
p_dbus_message_iter_close_container(&args, &aIter);
|
||||
}
|
||||
else
|
||||
goto err;
|
||||
if (timeout == 0)
|
||||
/* just set it to system default */
|
||||
expire_timeout = -1;
|
||||
else
|
||||
expire_timeout = max(min(timeout, BALLOON_SHOW_MAX_TIMEOUT), BALLOON_SHOW_MIN_TIMEOUT);
|
||||
|
||||
/* timeout */
|
||||
if (!p_dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, &expire_timeout ))
|
||||
goto err;
|
||||
if (!p_dbus_connection_send_with_reply (connection, msg, &pending, -1))
|
||||
goto err;
|
||||
if (!pending) goto err;
|
||||
|
||||
p_dbus_message_unref(msg);
|
||||
|
||||
p_dbus_pending_call_block(pending);
|
||||
|
||||
msg = p_dbus_pending_call_steal_reply(pending);
|
||||
p_dbus_pending_call_unref(pending);
|
||||
if (!msg) goto err;
|
||||
|
||||
if (p_dbus_set_error_from_message (&error, msg))
|
||||
{
|
||||
WARN("failed to create a notification - %s: %s\n", error.name, error.message);
|
||||
p_dbus_error_free( &error);
|
||||
goto err;
|
||||
}
|
||||
if (!p_dbus_message_iter_init(msg, &args))
|
||||
goto err;
|
||||
|
||||
if (DBUS_TYPE_UINT32 != p_dbus_message_iter_get_arg_type(&args))
|
||||
goto err;
|
||||
else if (p_new_id)
|
||||
p_dbus_message_iter_get_basic(&args, p_new_id);
|
||||
ret = TRUE;
|
||||
err:
|
||||
p_dbus_message_unref(msg);
|
||||
if (new_icon) NtUserDestroyCursor(new_icon, 0);
|
||||
free(icon_bits);
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOL snidrv_run_loop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
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
169
dlls/win32u/snidrv/image.c
Normal 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
|
40
dlls/win32u/snidrv/snidrv.h
Normal file
40
dlls/win32u/snidrv/snidrv.h
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue