dunst/dbus.c
Anton Lofgren 7a4d7fdd79 dbus: Fix iterator memory leak
The manual for g_variant_iter_next_value states the following:

"[..]
Use g_variant_unref() to drop your reference on the return value when
you no longer need it.
[..]"
https://developer.gnome.org/glib/unstable/glib-GVariant.html#g-variant-iter-next-value

For most notifications (I guess), this is not a problem, since the leak
is relatively tiny. However, for notifications that contain big chunks
of binary data, such as those sent out by the new Spotify client, it
adds up fast.

The Spotify notifications in particular contain a rather large
"icon_data" byte array (contained in a dict), which while never recorded
is never-the-less iterated over and allocated, which in turn gives rise to
issue #173.

This fixes #173.
2014-05-30 11:52:24 +02:00

427 lines
17 KiB
C

/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
#include <glib.h>
#include <gio/gio.h>
#include "dunst.h"
#include "dbus.h"
#include "notification.h"
GDBusConnection *dbus_conn;
static GDBusNodeInfo *introspection_data = NULL;
static const char *introspection_xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<node name=\"/org/freedesktop/Notifications\">"
" <interface name=\"org.freedesktop.Notifications\">"
" <method name=\"GetCapabilities\">"
" <arg direction=\"out\" name=\"capabilities\" type=\"as\"/>"
" </method>"
" <method name=\"Notify\">"
" <arg direction=\"in\" name=\"app_name\" type=\"s\"/>"
" <arg direction=\"in\" name=\"replaces_id\" type=\"u\"/>"
" <arg direction=\"in\" name=\"app_icon\" type=\"s\"/>"
" <arg direction=\"in\" name=\"summary\" type=\"s\"/>"
" <arg direction=\"in\" name=\"body\" type=\"s\"/>"
" <arg direction=\"in\" name=\"actions\" type=\"as\"/>"
" <arg direction=\"in\" name=\"hints\" type=\"a{sv}\"/>"
" <arg direction=\"in\" name=\"expire_timeout\" type=\"i\"/>"
" <arg direction=\"out\" name=\"id\" type=\"u\"/>"
" </method>"
" <method name=\"CloseNotification\">"
" <arg direction=\"in\" name=\"id\" type=\"u\"/>"
" </method>"
" <method name=\"GetServerInformation\">"
" <arg direction=\"out\" name=\"name\" type=\"s\"/>"
" <arg direction=\"out\" name=\"vendor\" type=\"s\"/>"
" <arg direction=\"out\" name=\"version\" type=\"s\"/>"
" <arg direction=\"out\" name=\"spec_version\" type=\"s\"/>"
" </method>"
" <signal name=\"NotificationClosed\">"
" <arg name=\"id\" type=\"u\"/>"
" <arg name=\"reason\" type=\"u\"/>"
" </signal>"
" <signal name=\"ActionInvoked\">"
" <arg name=\"id\" type=\"u\"/>"
" <arg name=\"action_key\" type=\"s\"/>"
" </signal>"
" </interface>"
"</node>";
static void onGetCapabilities(GDBusConnection * connection,
const gchar * sender,
const GVariant * parameters,
GDBusMethodInvocation * invocation);
static void onNotify(GDBusConnection * connection,
const gchar * sender,
GVariant * parameters, GDBusMethodInvocation * invocation);
static void onCloseNotification(GDBusConnection * connection,
const gchar * sender,
GVariant * parameters,
GDBusMethodInvocation * invocation);
static void onGetServerInformation(GDBusConnection * connection,
const gchar * sender,
const GVariant * parameters,
GDBusMethodInvocation * invocation);
void handle_method_call(GDBusConnection * connection,
const gchar * sender,
const gchar * object_path,
const gchar * interface_name,
const gchar * method_name,
GVariant * parameters,
GDBusMethodInvocation * invocation, gpointer user_data)
{
if (g_strcmp0(method_name, "GetCapabilities") == 0) {
onGetCapabilities(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "Notify") == 0) {
onNotify(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "CloseNotification") == 0) {
onCloseNotification(connection, sender, parameters, invocation);
} else if (g_strcmp0(method_name, "GetServerInformation") == 0) {
onGetServerInformation(connection, sender, parameters,
invocation);
} else {
printf("WARNING: sender: %s; unknown method_name: %s\n", sender,
method_name);
}
}
static void onGetCapabilities(GDBusConnection * connection,
const gchar * sender,
const GVariant * parameters,
GDBusMethodInvocation * invocation)
{
GVariantBuilder *builder;
GVariant *value;
builder = g_variant_builder_new(G_VARIANT_TYPE("as"));
g_variant_builder_add(builder, "s", "actions");
g_variant_builder_add(builder, "s", "body");
g_variant_builder_add(builder, "s", "body-markup");
value = g_variant_new("(as)", builder);
g_variant_builder_unref(builder);
g_dbus_method_invocation_return_value(invocation, value);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
g_variant_unref(value);
}
static void onNotify(GDBusConnection * connection,
const gchar * sender,
GVariant * parameters, GDBusMethodInvocation * invocation)
{
gchar *appname = NULL;
guint replaces_id = 0;
gchar *icon = NULL;
gchar *summary = NULL;
gchar *body = NULL;
Actions *actions = malloc(sizeof(Actions));
gint timeout = -1;
/* hints */
gint urgency = 1;
gint progress = -1;
gchar *fgcolor = NULL;
gchar *bgcolor = NULL;
gchar *category = NULL;
actions->actions = NULL;
actions->count = 0;
{
GVariantIter *iter = g_variant_iter_new(parameters);
GVariant *content;
GVariant *dict_value;
int idx = 0;
while ((content = g_variant_iter_next_value(iter))) {
switch (idx) {
case 0:
if (g_variant_is_of_type
(content, G_VARIANT_TYPE_STRING))
appname =
g_variant_dup_string(content, NULL);
break;
case 1:
if (g_variant_is_of_type
(content, G_VARIANT_TYPE_UINT32))
replaces_id =
g_variant_get_uint32(content);
break;
case 2:
if (g_variant_is_of_type
(content, G_VARIANT_TYPE_STRING))
icon =
g_variant_dup_string(content, NULL);
break;
case 3:
if (g_variant_is_of_type
(content, G_VARIANT_TYPE_STRING))
summary =
g_variant_dup_string(content, NULL);
break;
case 4:
if (g_variant_is_of_type
(content, G_VARIANT_TYPE_STRING))
body =
g_variant_dup_string(content, NULL);
break;
case 5:
if (g_variant_is_of_type
(content, G_VARIANT_TYPE_STRING_ARRAY))
actions->actions =
g_variant_dup_strv(content,
&(actions->
count));
break;
case 6:
if (g_variant_is_of_type
(content, G_VARIANT_TYPE_DICTIONARY)) {
dict_value =
g_variant_lookup_value(content,
"urgency",
G_VARIANT_TYPE_BYTE);
if (dict_value)
urgency =
g_variant_get_byte
(dict_value);
dict_value =
g_variant_lookup_value(content,
"fgcolor",
G_VARIANT_TYPE_STRING);
if (dict_value)
fgcolor =
g_variant_dup_string
(dict_value, NULL);
dict_value =
g_variant_lookup_value(content,
"bgcolor",
G_VARIANT_TYPE_STRING);
if (dict_value)
bgcolor =
g_variant_dup_string
(dict_value, NULL);
dict_value =
g_variant_lookup_value(content,
"category",
G_VARIANT_TYPE_STRING);
if (dict_value) {
category =
g_variant_dup_string(
dict_value, NULL);
}
dict_value =
g_variant_lookup_value(content,
"value",
G_VARIANT_TYPE_INT32);
if (dict_value) {
progress =
g_variant_get_int32(dict_value);
} else {
dict_value =
g_variant_lookup_value(content,
"value",
G_VARIANT_TYPE_UINT32);
if (dict_value)
progress =
g_variant_get_uint32(dict_value);
}
}
break;
case 7:
if (g_variant_is_of_type
(content, G_VARIANT_TYPE_INT32))
timeout = g_variant_get_int32(content);
break;
}
g_variant_unref(content);
idx++;
}
g_variant_iter_free(iter);
}
fflush(stdout);
if (timeout > 0) {
/* do some rounding */
timeout = (timeout + 500) / 1000;
if (timeout < 1) {
timeout = 1;
}
}
notification *n = malloc(sizeof(notification));
n->appname = appname;
n->summary = summary;
n->body = body;
n->icon = icon;
n->timeout = timeout;
n->progress = (progress < 0 || progress > 100) ? 0 : progress + 1;
n->urgency = urgency;
n->category = category;
n->dbus_client = strdup(sender);
if (actions->count > 0) {
n->actions = actions;
} else {
n->actions = NULL;
free(actions);
}
for (int i = 0; i < ColLast; i++) {
n->color_strings[i] = NULL;
}
n->color_strings[ColFG] = fgcolor;
n->color_strings[ColBG] = bgcolor;
int id = notification_init(n, replaces_id);
wake_up();
GVariant *reply = g_variant_new("(u)", id);
g_dbus_method_invocation_return_value(invocation, reply);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
run(NULL);
}
static void onCloseNotification(GDBusConnection * connection,
const gchar * sender,
GVariant * parameters,
GDBusMethodInvocation * invocation)
{
guint32 id;
g_variant_get(parameters, "(u)", &id);
notification_close_by_id(id, 3);
g_dbus_method_invocation_return_value(invocation, NULL);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
static void onGetServerInformation(GDBusConnection * connection,
const gchar * sender,
const GVariant * parameters,
GDBusMethodInvocation * invocation)
{
GVariant *value;
value = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2");
g_dbus_method_invocation_return_value(invocation, value);
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}
void notificationClosed(notification * n, int reason)
{
if (!dbus_conn) {
printf("DEBUG: notificationClosed but not (yet) connected\n");
return;
}
GVariant *body = g_variant_new("(uu)", n->id, reason);
GError *err = NULL;
g_dbus_connection_emit_signal(dbus_conn,
n->dbus_client,
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"NotificationClosed", body, &err);
if (err) {
printf("notificationClosed ERROR\n");
}
}
void actionInvoked(notification * n, const char *identifier)
{
GVariant *body = g_variant_new("(us)", n->id, identifier);
GError *err = NULL;
g_dbus_connection_emit_signal(dbus_conn,
n->dbus_client,
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"ActionInvoked", body, &err);
if (err) {
printf("ActionInvoked ERROR\n");
}
}
static const GDBusInterfaceVTable interface_vtable = {
handle_method_call
};
static void on_bus_acquired(GDBusConnection * connection,
const gchar * name, gpointer user_data)
{
guint registration_id;
registration_id = g_dbus_connection_register_object(connection,
"/org/freedesktop/Notifications",
introspection_data->
interfaces[0],
&interface_vtable,
NULL, NULL, NULL);
if (!registration_id > 0) {
fprintf(stderr, "Unable to register\n");
exit(1);
}
}
static void on_name_acquired(GDBusConnection * connection,
const gchar * name, gpointer user_data)
{
dbus_conn = connection;
}
static void on_name_lost(GDBusConnection * connection,
const gchar * name, gpointer user_data)
{
fprintf(stderr, "Name Lost. Is Another notification daemon running?\n");
exit(1);
}
int initdbus(void)
{
guint owner_id;
#if !GLIB_CHECK_VERSION(2,35,0)
g_type_init();
#endif
introspection_data = g_dbus_node_info_new_for_xml(introspection_xml,
NULL);
owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
"org.freedesktop.Notifications",
G_BUS_NAME_OWNER_FLAGS_NONE,
on_bus_acquired,
on_name_acquired, on_name_lost, NULL, NULL);
return owner_id;
}
void dbus_tear_down(int owner_id)
{
g_bus_unown_name(owner_id);
}
/* vim: set ts=8 sw=8 tw=0: */