libnotify/libnotify/notify.c

812 lines
18 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.
*/
#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-compat.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
{
int frames;
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 = 0;
g_hash_table_insert(_handles, &handle->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_append(message, iter);
return message;
}
static void
_notify_dbus_message_iter_append_string_or_empty(DBusMessageIter *iter,
const char *str)
{
g_return_if_fail(iter != NULL);
if (str == NULL)
str = "";
_notify_dbus_message_iter_append_string(iter, str);
}
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);
_notify_dbus_message_iter_get_uint32(&iter, id);
dbus_message_iter_next(&iter);
_notify_dbus_message_iter_get_uint32(&iter, reason);
g_hash_table_remove(_handles, &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);
_notify_dbus_message_iter_get_uint32(&iter, id);
dbus_message_iter_next(&iter);
_notify_dbus_message_iter_get_uint32(&iter, action_id);
handle = g_hash_table_lookup(_handles, &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, &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_start_service_by_name(_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_DBUS ","
"sender=" DBUS_SERVICE_DBUS ,
&error);
dbus_bus_add_match(_dbus_conn,
"type=signal,"
"interface=" NOTIFY_DBUS_CORE_INTERFACE ","
"path=" NOTIFY_DBUS_CORE_OBJECT ,
&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_disconnect(_dbus_conn);
dbus_connection_unref(_dbus_conn);
_dbus_conn = NULL;
}
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_int_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)
{
_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);
_notify_dbus_message_iter_append_uint32(&iter, handle->id);
dbus_connection_send_with_reply_and_block(_dbus_conn, message, -1, 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);
_notify_dbus_message_iter_get_string(&iter, name);
dbus_message_iter_next(&iter);
_notify_dbus_message_iter_get_string(&iter, vendor);
dbus_message_iter_next(&iter);
_notify_dbus_message_iter_get_string(&iter, version);
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);
_notify_dbus_message_iter_get_string_array(&iter, &temp_array,
&num_items);
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()
{
NotifyIcon *icon;
icon = g_new0(NotifyIcon, 1);
return icon;
}
NotifyIcon *
notify_icon_new_from_uri(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->frames = 1;
icon->uri = g_malloc(sizeof(char *));
icon->uri[0] = g_strdup(icon_uri);
return icon;
}
NotifyIcon *
notify_icon_new_from_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->frames = 1;
icon->raw_len = g_malloc(sizeof(icon->raw_len));
icon->raw_len[0] = icon_len;
icon->raw_data = g_malloc(sizeof(guchar *));
icon->raw_data[0] = g_memdup(icon_data, icon_len);
return icon;
}
gboolean
notify_icon_add_frame_from_data(NotifyIcon *icon, size_t icon_len, const guchar *icon_data)
{
g_return_val_if_fail(icon != NULL, FALSE);
g_return_val_if_fail(icon_data != NULL, FALSE);
g_return_val_if_fail(icon_len != 0, FALSE);
if (icon->frames) g_return_val_if_fail(icon->raw_len != NULL, FALSE);
icon->frames++;
icon->raw_len = g_realloc(icon->raw_len, sizeof(size_t) * icon->frames);
icon->raw_len[icon->frames - 1] = icon_len;
icon->raw_data = g_realloc(icon->raw_data, sizeof(guchar *) * icon->frames);
icon->raw_data[icon->frames - 1] = g_memdup(icon_data, icon_len);
return TRUE;
}
gboolean
notify_icon_add_frame_from_uri(NotifyIcon *icon, const char *uri)
{
g_return_val_if_fail(icon != NULL, FALSE);
g_return_val_if_fail(uri != NULL, FALSE);
if (icon->frames)
{
g_return_val_if_fail(icon->uri != NULL, FALSE);
}
icon->frames++;
icon->uri = g_realloc(icon->uri, sizeof(char *) * icon->frames);
icon->uri[icon->frames - 1] = g_strdup(uri);
return TRUE;
}
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(NotifyHandle *replaces, const char *type,
NotifyUrgency urgency, const char *summary,
const char *body, const NotifyIcon *icon,
gboolean expires, time_t timeout,
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, type, urgency, summary,
body, icon, expires,
timeout, user_data,
action_count, actions);
va_end(actions);
return handle;
}
NotifyHandle *
notify_send_notification_varg(NotifyHandle *replaces, const char *type,
NotifyUrgency urgency, const char *summary,
const char *body, const NotifyIcon *icon,
gboolean expires, time_t timeout,
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;
guint32 replaces_id;
guint32 timeout_time;
NotifyHandle *handle;
g_return_val_if_fail(notify_is_initted(), NULL);
message = _notify_dbus_message_new("Notify", &iter);
g_return_val_if_fail(message != NULL, NULL);
replaces_id = (replaces != NULL ? replaces->id : 0);
_notify_dbus_message_iter_append_string_or_empty(&iter, _app_name);
_notify_dbus_message_iter_append_string_or_empty(&iter, NULL);
_notify_dbus_message_iter_append_uint32(&iter, replaces_id);
_notify_dbus_message_iter_append_string_or_empty(&iter, type);
_notify_dbus_message_iter_append_byte(&iter, urgency);
_notify_dbus_message_iter_append_string(&iter, summary);
_notify_dbus_message_iter_append_string_or_empty(&iter, body);
if (icon == NULL)
{
_notify_dbus_message_iter_append_string_or_empty(&iter, NULL);
}
else if (icon->raw_data)
{
int i;
#if NOTIFY_CHECK_DBUS_VERSION(0, 30)
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_ARRAY_AS_STRING,
&array_iter);
#else
dbus_message_iter_append_array(&iter, &array_iter, DBUS_TYPE_ARRAY);
#endif
for (i = 0; i < icon->frames; i++)
{
_notify_dbus_message_iter_append_byte_array(&array_iter,
icon->raw_data[i],
icon->raw_len[i]);
}
dbus_message_iter_close_container(&iter, &array_iter);
}
else
{
int i;
g_assert(icon->uri != NULL); /* can be either raw data OR uri */
#if NOTIFY_CHECK_DBUS_VERSION(0, 30)
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_STRING_AS_STRING,
&array_iter);
#else
dbus_message_iter_append_array(&iter, &array_iter, DBUS_TYPE_STRING);
#endif
for (i = 0; i < icon->frames; i++)
{
_notify_dbus_message_iter_append_string(&array_iter,
icon->uri[i]);
}
dbus_message_iter_close_container(&iter, &array_iter);
}
/* Actions */
#if NOTIFY_CHECK_DBUS_VERSION(0, 30)
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_UINT32_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&dict_iter);
#else
dbus_message_iter_append_dict(&iter, &dict_iter);
#endif
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;
#if NOTIFY_CHECK_DBUS_VERSION(0, 30)
DBusMessageIter entry_iter;
#endif
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);
#if NOTIFY_CHECK_DBUS_VERSION(0, 30)
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY,
NULL, &entry_iter);
dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING,
&action->text);
dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_UINT32,
&action->id);
dbus_message_iter_close_container(&dict_iter, &entry_iter);
#else
dbus_message_iter_append_dict_key(&dict_iter, action->text);
dbus_message_iter_append_uint32(&dict_iter, action->id);
#endif
g_hash_table_insert(table, &action->id, action);
}
dbus_message_iter_close_container(&iter, &array_iter);
/* Hints */
#if NOTIFY_CHECK_DBUS_VERSION(0, 30)
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_UINT32_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&dict_iter);
dbus_message_iter_close_container(&iter, &dict_iter);
#else
dbus_message_iter_append_dict(&iter, &dict_iter);
#endif
/* Expires */
_notify_dbus_message_iter_append_boolean(&iter, expires);
/* Expire Timeout */
timeout_time = (expires ? timeout : 0);
_notify_dbus_message_iter_append_uint32(&iter, timeout_time);
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 notification: %s\n", error.message);
dbus_error_free(&error);
g_hash_table_destroy(table);
return 0;
}
dbus_message_iter_init(reply, &iter);
_notify_dbus_message_iter_get_uint32(&iter, id);
dbus_message_unref(reply);
dbus_error_free(&error);
handle = _notify_handle_new(id);
handle->actions_table = table;
handle->action_count = action_count;
return handle;
}