704 lines
15 KiB
C
704 lines
15 KiB
C
/** -*- mode: c-mode; tab-width: 4; indent-tabs-mode: t; -*-
|
|
*
|
|
* @file libnotify/notify.c Notifications library
|
|
*
|
|
* @Copyright (C) 2004 Christian Hammond <chipx86@chipx86.com>
|
|
* @Copyright (C) 2004 Mike Hearn <mike@navi.cx>
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/* FIXME: do we want so many arguments in the API? */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifndef DBUS_API_SUBJECT_TO_CHANGE
|
|
# define DBUS_API_SUBJECT_TO_CHANGE 1
|
|
#endif
|
|
|
|
#include "notify.h"
|
|
#include <dbus/dbus.h>
|
|
#include <dbus/dbus-glib.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
|
|
#define NOTIFY_DBUS_SERVICE "org.freedesktop.Notifications"
|
|
#define NOTIFY_DBUS_CORE_INTERFACE "org.freedesktop.Notifications"
|
|
#define NOTIFY_DBUS_CORE_OBJECT "/org/freedesktop/Notifications"
|
|
|
|
struct _NotifyHandle
|
|
{
|
|
guint32 id;
|
|
|
|
guint32 replaces;
|
|
|
|
gpointer user_data;
|
|
|
|
guint32 action_count;
|
|
GHashTable *actions_table;
|
|
};
|
|
|
|
struct _NotifyIcon
|
|
{
|
|
char *uri;
|
|
|
|
size_t raw_len;
|
|
guchar *raw_data;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
guint32 id;
|
|
char *text;
|
|
NotifyCallback cb;
|
|
|
|
} NotifyAction;
|
|
|
|
static DBusConnection *_dbus_conn = NULL;
|
|
static gboolean _initted = FALSE;
|
|
static gboolean _filters_added = FALSE;
|
|
static guint32 _init_ref_count = 0;
|
|
static char *_app_name = NULL;
|
|
static GHashTable *_handles = NULL;
|
|
|
|
#ifdef __GNUC__
|
|
# define format_func __attribute__((format(printf, 1, 2)))
|
|
#else /* no format string checking with this compiler */
|
|
# define format_func
|
|
#endif
|
|
|
|
static void format_func
|
|
print_error(char *message, ...)
|
|
{
|
|
char buf[1024];
|
|
va_list args;
|
|
|
|
va_start(args, message);
|
|
vsnprintf(buf, sizeof(buf), message, args);
|
|
va_end(args);
|
|
|
|
fprintf(stderr, "%s(%d): libnotify: %s", getenv("_") ? getenv("_") : "", getpid(), buf);
|
|
}
|
|
|
|
static NotifyHandle *
|
|
_notify_handle_new(guint32 id)
|
|
{
|
|
NotifyHandle *handle;
|
|
|
|
handle = g_new0(NotifyHandle, 1);
|
|
|
|
handle->id = id;
|
|
|
|
handle->replaces = -1;
|
|
|
|
g_hash_table_insert(_handles, GINT_TO_POINTER(id), handle);
|
|
|
|
return handle;
|
|
}
|
|
|
|
static void
|
|
_notify_handle_destroy(NotifyHandle *handle)
|
|
{
|
|
g_return_if_fail(handle != NULL);
|
|
|
|
if (handle->actions_table != NULL)
|
|
g_hash_table_destroy(handle->actions_table);
|
|
|
|
g_free(handle);
|
|
}
|
|
|
|
static void
|
|
_notify_action_destroy(NotifyAction *action)
|
|
{
|
|
g_return_if_fail(action != NULL);
|
|
|
|
if (action->text != NULL)
|
|
g_free(action->text);
|
|
|
|
g_free(action);
|
|
}
|
|
|
|
static DBusMessage *
|
|
_notify_dbus_message_new(const char *name, DBusMessageIter *iter)
|
|
{
|
|
DBusMessage *message;
|
|
|
|
g_return_val_if_fail(name != NULL, NULL);
|
|
g_return_val_if_fail(*name != '\0', NULL);
|
|
|
|
message = dbus_message_new_method_call(NOTIFY_DBUS_SERVICE,
|
|
NOTIFY_DBUS_CORE_OBJECT,
|
|
NOTIFY_DBUS_CORE_INTERFACE,
|
|
name);
|
|
|
|
g_return_val_if_fail(message != NULL, NULL);
|
|
|
|
if (iter != NULL)
|
|
dbus_message_iter_init(message, iter);
|
|
|
|
return message;
|
|
}
|
|
|
|
static void
|
|
_notify_dbus_message_iter_append_string_or_nil(DBusMessageIter *iter,
|
|
const char *str)
|
|
{
|
|
g_return_if_fail(iter != NULL);
|
|
|
|
if (str == NULL)
|
|
dbus_message_iter_append_nil(iter);
|
|
else
|
|
dbus_message_iter_append_string(iter, str);
|
|
}
|
|
|
|
#if 0
|
|
static char *
|
|
_notify_dbus_message_iter_get_string_or_nil(DBusMessageIter *iter)
|
|
{
|
|
int arg_type;
|
|
|
|
g_return_val_if_fail(iter != NULL, NULL);
|
|
|
|
arg_type = dbus_message_iter_get_arg_type(iter);
|
|
|
|
if (arg_type == DBUS_TYPE_STRING)
|
|
return dbus_message_iter_get_string(iter);
|
|
|
|
g_return_val_if_fail(arg_type == DBUS_TYPE_NIL, NULL);
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
static void
|
|
_notify_dbus_message_iter_append_app_info(DBusMessageIter *iter)
|
|
{
|
|
g_return_if_fail(iter != NULL);
|
|
|
|
dbus_message_iter_append_string(iter, _app_name);
|
|
dbus_message_iter_append_nil(iter); /* App Icon */
|
|
}
|
|
#endif
|
|
|
|
static DBusHandlerResult
|
|
_filter_func(DBusConnection *dbus_conn, DBusMessage *message, void *user_data)
|
|
{
|
|
DBusMessageIter iter;
|
|
|
|
if (dbus_message_is_signal(message, NOTIFY_DBUS_CORE_INTERFACE,
|
|
"NotificationClosed"))
|
|
{
|
|
guint32 id, reason;
|
|
|
|
dbus_message_iter_init(message, &iter);
|
|
|
|
id = dbus_message_iter_get_uint32(&iter);
|
|
dbus_message_iter_next(&iter);
|
|
|
|
reason = dbus_message_iter_get_uint32(&iter);
|
|
|
|
g_hash_table_remove(_handles, GINT_TO_POINTER(id));
|
|
}
|
|
else if (dbus_message_is_signal(message, NOTIFY_DBUS_CORE_INTERFACE,
|
|
"ActionInvoked"))
|
|
{
|
|
guint32 id, action_id;
|
|
NotifyHandle *handle;
|
|
|
|
dbus_message_iter_init(message, &iter);
|
|
|
|
id = dbus_message_iter_get_uint32(&iter);
|
|
dbus_message_iter_next(&iter);
|
|
|
|
action_id = dbus_message_iter_get_uint32(&iter);
|
|
|
|
handle = g_hash_table_lookup(_handles, GINT_TO_POINTER(id));
|
|
|
|
if (handle->actions_table == NULL)
|
|
{
|
|
print_error("An action (%d) was invoked for a notification (%d) "
|
|
"with no actions listed!\n",
|
|
action_id, id);
|
|
}
|
|
else
|
|
{
|
|
NotifyAction *action;
|
|
|
|
action = g_hash_table_lookup(handle->actions_table,
|
|
GINT_TO_POINTER(action_id));
|
|
|
|
if (action == NULL)
|
|
{
|
|
print_error("An invalid action (%d) was invoked for "
|
|
"notification %d\n", action_id, id);
|
|
}
|
|
else if (action->cb != NULL)
|
|
{
|
|
action->cb(handle, action_id, handle->user_data);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static gboolean
|
|
_notify_connect(void)
|
|
{
|
|
DBusError error;
|
|
|
|
dbus_error_init(&error);
|
|
|
|
_dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &error);
|
|
|
|
if (_dbus_conn == NULL)
|
|
{
|
|
print_error("Error connecting to session bus: %s\n", error.message);
|
|
|
|
dbus_error_free(&error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
dbus_connection_set_exit_on_disconnect(_dbus_conn, FALSE);
|
|
|
|
if (!dbus_bus_activate_service(_dbus_conn, NOTIFY_DBUS_SERVICE, 0, NULL, &error))
|
|
{
|
|
print_error("Error activating %s service: %s\n",
|
|
NOTIFY_DBUS_SERVICE, error.message);
|
|
|
|
dbus_error_free(&error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (!dbus_connection_add_filter(_dbus_conn, _filter_func, NULL, NULL))
|
|
{
|
|
print_error("Error creating D-BUS message filter.\n");
|
|
|
|
dbus_error_free(&error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
dbus_bus_add_match(_dbus_conn,
|
|
"type='signal',"
|
|
"interface='" DBUS_INTERFACE_ORG_FREEDESKTOP_DBUS "',"
|
|
"sender='" DBUS_SERVICE_ORG_FREEDESKTOP_DBUS "'",
|
|
&error);
|
|
|
|
if (dbus_error_is_set(&error))
|
|
{
|
|
print_error("Error subscribing to signals: %s\n", error.message);
|
|
|
|
dbus_error_free(&error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
_filters_added = TRUE;
|
|
|
|
dbus_error_free(&error);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
_notify_disconnect(void)
|
|
{
|
|
if (_dbus_conn == NULL)
|
|
return;
|
|
|
|
if (_filters_added)
|
|
{
|
|
dbus_connection_remove_filter(_dbus_conn, _filter_func, NULL);
|
|
|
|
_filters_added = FALSE;
|
|
}
|
|
|
|
dbus_connection_unref(_dbus_conn);
|
|
}
|
|
|
|
gboolean
|
|
notify_init(const char *app_name)
|
|
{
|
|
g_return_val_if_fail(app_name != NULL, FALSE);
|
|
g_return_val_if_fail(*app_name != '\0', FALSE);
|
|
|
|
_init_ref_count++;
|
|
|
|
if (_initted)
|
|
return TRUE;
|
|
|
|
if (!_notify_connect())
|
|
{
|
|
_notify_disconnect();
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
_app_name = g_strdup(app_name);
|
|
|
|
_handles = g_hash_table_new_full(g_direct_hash, g_int_equal,
|
|
NULL, (GFreeFunc)_notify_handle_destroy);
|
|
|
|
#ifdef HAVE_ATEXIT
|
|
atexit(notify_uninit);
|
|
#endif /* HAVE_ATEXIT */
|
|
|
|
_initted = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
notify_uninit(void)
|
|
{
|
|
g_return_if_fail(notify_is_initted());
|
|
|
|
_init_ref_count--;
|
|
|
|
if (_init_ref_count > 0)
|
|
return;
|
|
|
|
if (_app_name != NULL)
|
|
{
|
|
g_free(_app_name);
|
|
_app_name = NULL;
|
|
}
|
|
|
|
if (_handles != NULL)
|
|
{
|
|
g_hash_table_destroy(_handles);
|
|
_handles = NULL;
|
|
}
|
|
|
|
_notify_disconnect();
|
|
}
|
|
|
|
gboolean
|
|
notify_is_initted(void)
|
|
{
|
|
return _initted;
|
|
}
|
|
|
|
void
|
|
notify_close(NotifyHandle *handle)
|
|
{
|
|
DBusMessage *message;
|
|
DBusMessageIter iter;
|
|
|
|
g_return_if_fail(handle != NULL);
|
|
|
|
message = _notify_dbus_message_new("CloseNotification", &iter);
|
|
|
|
g_return_if_fail(message != NULL);
|
|
|
|
dbus_message_iter_append_uint32(&iter, handle->id);
|
|
|
|
dbus_connection_send(_dbus_conn, message, NULL);
|
|
dbus_message_unref(message);
|
|
}
|
|
|
|
gboolean
|
|
notify_get_server_info(char **ret_name, char **ret_vendor, char **ret_version)
|
|
{
|
|
DBusMessage *message, *reply;
|
|
DBusMessageIter iter;
|
|
DBusError error;
|
|
char *name, *vendor, *version;
|
|
|
|
message = _notify_dbus_message_new("GetServerInformation", NULL);
|
|
|
|
g_return_val_if_fail(message != NULL, FALSE);
|
|
|
|
dbus_error_init(&error);
|
|
|
|
reply = dbus_connection_send_with_reply_and_block(_dbus_conn, message,
|
|
-1, &error);
|
|
|
|
dbus_message_unref(message);
|
|
|
|
if (dbus_error_is_set(&error))
|
|
{
|
|
print_error("Error sending GetServerInformation: %s\n", error.message);
|
|
|
|
dbus_error_free(&error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
dbus_error_free(&error);
|
|
|
|
dbus_message_iter_init(reply, &iter);
|
|
|
|
name = dbus_message_iter_get_string(&iter);
|
|
dbus_message_iter_next(&iter);
|
|
|
|
vendor = dbus_message_iter_get_string(&iter);
|
|
dbus_message_iter_next(&iter);
|
|
|
|
version = dbus_message_iter_get_string(&iter);
|
|
dbus_message_iter_next(&iter);
|
|
|
|
dbus_message_unref(reply);
|
|
|
|
if (ret_name != NULL)
|
|
*ret_name = g_strdup(name);
|
|
|
|
if (ret_vendor != NULL)
|
|
*ret_vendor = g_strdup(vendor);
|
|
|
|
if (ret_version != NULL)
|
|
*ret_version = g_strdup(version);
|
|
|
|
dbus_free(name);
|
|
dbus_free(vendor);
|
|
dbus_free(version);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GList *
|
|
notify_get_server_caps(void)
|
|
{
|
|
DBusMessage *message, *reply;
|
|
DBusMessageIter iter;
|
|
DBusError error;
|
|
GList *caps = NULL;
|
|
char **temp_array;
|
|
int num_items, i;
|
|
|
|
message = _notify_dbus_message_new("GetCapabilities", NULL);
|
|
|
|
g_return_val_if_fail(message != NULL, FALSE);
|
|
|
|
dbus_error_init(&error);
|
|
|
|
reply = dbus_connection_send_with_reply_and_block(_dbus_conn, message,
|
|
-1, &error);
|
|
|
|
dbus_message_unref(message);
|
|
|
|
if (dbus_error_is_set(&error))
|
|
{
|
|
print_error("Error sending GetCapabilities: %s\n", error.message);
|
|
|
|
dbus_error_free(&error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
dbus_error_free(&error);
|
|
|
|
dbus_message_iter_init(reply, &iter);
|
|
|
|
if (!dbus_message_iter_get_string_array(&iter, &temp_array, &num_items))
|
|
return NULL;
|
|
|
|
dbus_message_unref(reply);
|
|
|
|
for (i = 0; i < num_items; i++)
|
|
caps = g_list_append(caps, g_strdup(temp_array[i]));
|
|
|
|
dbus_free_string_array(temp_array);
|
|
|
|
return caps;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* Icon API
|
|
**************************************************************************/
|
|
NotifyIcon *
|
|
notify_icon_new(const char *icon_uri)
|
|
{
|
|
NotifyIcon *icon;
|
|
|
|
g_return_val_if_fail(icon_uri != NULL, NULL);
|
|
g_return_val_if_fail(*icon_uri != '\0', NULL);
|
|
|
|
icon = g_new0(NotifyIcon, 1);
|
|
|
|
icon->uri = g_strdup(icon_uri);
|
|
|
|
return icon;
|
|
}
|
|
|
|
NotifyIcon *
|
|
notify_icon_new_with_data(size_t icon_len, const guchar *icon_data)
|
|
{
|
|
NotifyIcon *icon;
|
|
|
|
g_return_val_if_fail(icon_len > 0, NULL);
|
|
g_return_val_if_fail(icon_data != NULL, NULL);
|
|
|
|
icon = g_new0(NotifyIcon, 1);
|
|
|
|
icon->raw_len = icon_len;
|
|
icon->raw_data = g_memdup(icon_data, icon_len);
|
|
|
|
return icon;
|
|
}
|
|
|
|
void
|
|
notify_icon_destroy(NotifyIcon *icon)
|
|
{
|
|
g_return_if_fail(icon != NULL);
|
|
|
|
if (icon->uri != NULL)
|
|
g_free(icon->uri);
|
|
|
|
if (icon->raw_data != NULL)
|
|
g_free(icon->raw_data);
|
|
|
|
g_free(icon);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* Notifications API
|
|
**************************************************************************/
|
|
NotifyHandle *
|
|
notify_send_notification(guint32 replaces, NotifyUrgency urgency, const char *summary,
|
|
const char *detailed, const NotifyIcon *icon,
|
|
gboolean expires, time_t expire_time,
|
|
gpointer user_data, size_t action_count, ...)
|
|
{
|
|
va_list actions;
|
|
NotifyHandle *handle;
|
|
|
|
g_return_val_if_fail(summary != NULL, 0);
|
|
|
|
va_start(actions, action_count);
|
|
handle = notify_send_notification_varg(replaces, urgency, summary, detailed, icon,
|
|
expires, expire_time, user_data,
|
|
action_count, actions);
|
|
va_end(actions);
|
|
|
|
return handle;
|
|
}
|
|
|
|
NotifyHandle *
|
|
notify_send_notification_varg(guint32 replaces, NotifyUrgency urgency, const char *summary,
|
|
const char *detailed, const NotifyIcon *icon,
|
|
gboolean expires, time_t expire_time,
|
|
gpointer user_data, size_t action_count,
|
|
va_list actions)
|
|
{
|
|
DBusMessage *message, *reply;
|
|
DBusMessageIter iter, array_iter, dict_iter;
|
|
DBusError error;
|
|
GHashTable *table;
|
|
guint32 id;
|
|
guint32 i;
|
|
NotifyHandle *handle;
|
|
|
|
message = _notify_dbus_message_new("Notify", &iter);
|
|
|
|
g_return_val_if_fail(message != NULL, 0);
|
|
|
|
#if 0
|
|
_notify_dbus_message_iter_append_app_info(&iter);
|
|
#endif
|
|
dbus_message_iter_append_uint32(&iter, replaces);
|
|
dbus_message_iter_append_byte(&iter, urgency);
|
|
dbus_message_iter_append_string(&iter, summary);
|
|
_notify_dbus_message_iter_append_string_or_nil(&iter, detailed);
|
|
|
|
/*
|
|
* NOTE: D-BUS 0.22cvs is the first to allow empty arrays, *I think*.
|
|
* For now, allow a NIL.
|
|
*/
|
|
if (icon == NULL)
|
|
dbus_message_iter_append_nil(&iter);
|
|
else if (icon->raw_len > 0 && icon->raw_data != NULL)
|
|
{
|
|
dbus_message_iter_append_array(&iter, &array_iter, DBUS_TYPE_ARRAY);
|
|
dbus_message_iter_append_byte_array(&array_iter, icon->raw_data,
|
|
icon->raw_len);
|
|
}
|
|
else
|
|
{
|
|
dbus_message_iter_append_array(&iter, &array_iter, DBUS_TYPE_STRING);
|
|
dbus_message_iter_append_string(&array_iter, icon->uri);
|
|
}
|
|
|
|
dbus_message_iter_append_nil(&iter); /* Sound */
|
|
|
|
/* Actions */
|
|
dbus_message_iter_append_dict(&iter, &dict_iter);
|
|
|
|
table = g_hash_table_new_full(g_int_hash, g_int_equal, NULL,
|
|
(GFreeFunc)_notify_action_destroy);
|
|
|
|
for (i = 0; i < action_count; i++)
|
|
{
|
|
NotifyAction *action;
|
|
|
|
action = g_new0(NotifyAction, 1);
|
|
|
|
action->id = va_arg(actions, guint32);
|
|
action->text = g_strdup((va_arg(actions, char *)));
|
|
action->cb = va_arg(actions, NotifyCallback);
|
|
|
|
dbus_message_iter_append_dict_key(&dict_iter, action->text);
|
|
dbus_message_iter_append_uint32(&dict_iter, action->id);
|
|
|
|
g_hash_table_insert(table, GINT_TO_POINTER(action->id), action);
|
|
}
|
|
|
|
if (expires)
|
|
dbus_message_iter_append_uint32(&iter, expire_time);
|
|
else
|
|
dbus_message_iter_append_nil(&iter);
|
|
|
|
dbus_error_init(&error);
|
|
|
|
reply = dbus_connection_send_with_reply_and_block(_dbus_conn, message,
|
|
-1, &error);
|
|
|
|
dbus_message_unref(message);
|
|
|
|
if (dbus_error_is_set(&error))
|
|
{
|
|
print_error("Error sending Notify: %s\n", error.message);
|
|
|
|
dbus_error_free(&error);
|
|
|
|
g_hash_table_destroy(table);
|
|
|
|
return 0;
|
|
}
|
|
|
|
dbus_message_iter_init(reply, &iter);
|
|
id = dbus_message_iter_get_uint32(&iter);
|
|
|
|
dbus_message_unref(reply);
|
|
dbus_error_free(&error);
|
|
|
|
handle = _notify_handle_new(id);
|
|
handle->actions_table = table;
|
|
handle->action_count = action_count;
|
|
|
|
return handle;
|
|
}
|