864 lines
33 KiB
C
864 lines
33 KiB
C
/* copyright 2013 Sascha Kruse and contributors (see LICENSE for licensing information) */
|
|
#include "dbus.h"
|
|
|
|
#include <gio/gio.h>
|
|
#include <glib.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "dunst.h"
|
|
#include "log.h"
|
|
#include "menu.h"
|
|
#include "notification.h"
|
|
#include "queues.h"
|
|
#include "settings.h"
|
|
#include "utils.h"
|
|
|
|
#define FDN_PATH "/org/freedesktop/Notifications"
|
|
#define FDN_IFAC "org.freedesktop.Notifications"
|
|
#define FDN_NAME "org.freedesktop.Notifications"
|
|
|
|
#define DUNST_PATH "/org/freedesktop/Notifications"
|
|
#define DUNST_IFAC "org.dunstproject.cmd0"
|
|
#define DUNST_NAME "org.freedesktop.Notifications"
|
|
|
|
#define PROPERTIES_IFAC "org.freedesktop.DBus.Properties"
|
|
|
|
GDBusConnection *dbus_conn;
|
|
|
|
static GDBusNodeInfo *introspection_data = NULL;
|
|
|
|
static const char *introspection_xml =
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
|
"<node name=\""FDN_PATH"\">"
|
|
" <interface name=\""FDN_IFAC"\">"
|
|
|
|
" <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>"
|
|
" <interface name=\""DUNST_IFAC"\">"
|
|
|
|
" <method name=\"ContextMenuCall\" />"
|
|
// TODO: add an optional parmater definining the action of notification number X to invoke
|
|
" <method name=\"NotificationAction\">"
|
|
" <arg name=\"number\" type=\"i\"/>"
|
|
" </method>"
|
|
" <method name=\"NotificationCloseLast\" />"
|
|
" <method name=\"NotificationCloseAll\" />"
|
|
" <method name=\"NotificationShow\" />"
|
|
" <method name=\"Ping\" />"
|
|
|
|
" <property name=\"running\" type=\"b\" access=\"readwrite\">"
|
|
" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
|
|
" </property>"
|
|
|
|
" </interface>"
|
|
"</node>";
|
|
|
|
static const char *stack_tag_hints[] = {
|
|
"synchronous",
|
|
"private-synchronous",
|
|
"x-canonical-private-synchronous",
|
|
"x-dunst-stack-tag"
|
|
};
|
|
|
|
struct dbus_method {
|
|
const char *method_name;
|
|
void (*method) (GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation);
|
|
};
|
|
|
|
// TODO: call it interface methods
|
|
#define DBUS_METHOD(name) static void dbus_cb_##name( \
|
|
GDBusConnection *connection, \
|
|
const gchar *sender, \
|
|
GVariant *parameters, \
|
|
GDBusMethodInvocation *invocation)
|
|
|
|
int cmp_methods(const void *vkey, const void *velem)
|
|
{
|
|
const char *key = (const char*)vkey;
|
|
const struct dbus_method *m = (const struct dbus_method*)velem;
|
|
|
|
return strcmp(key, m->method_name);
|
|
}
|
|
|
|
DBUS_METHOD(Notify);
|
|
DBUS_METHOD(CloseNotification);
|
|
DBUS_METHOD(GetCapabilities);
|
|
DBUS_METHOD(GetServerInformation);
|
|
static struct dbus_method methods_fdn[] = {
|
|
{"CloseNotification", dbus_cb_CloseNotification},
|
|
{"GetCapabilities", dbus_cb_GetCapabilities},
|
|
{"GetServerInformation", dbus_cb_GetServerInformation},
|
|
{"Notify", dbus_cb_Notify},
|
|
};
|
|
|
|
void dbus_cb_fdn_methods(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *method_name,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation,
|
|
gpointer user_data)
|
|
{
|
|
|
|
struct dbus_method *m = bsearch(method_name,
|
|
methods_fdn,
|
|
G_N_ELEMENTS(methods_fdn),
|
|
sizeof(struct dbus_method),
|
|
cmp_methods);
|
|
|
|
if (m) {
|
|
m->method(connection, sender, parameters, invocation);
|
|
} else {
|
|
LOG_M("Unknown method name: '%s' (sender: '%s').",
|
|
method_name,
|
|
sender);
|
|
}
|
|
}
|
|
|
|
DBUS_METHOD(dunst_ContextMenuCall);
|
|
DBUS_METHOD(dunst_NotificationAction);
|
|
DBUS_METHOD(dunst_NotificationCloseAll);
|
|
DBUS_METHOD(dunst_NotificationCloseLast);
|
|
DBUS_METHOD(dunst_NotificationShow);
|
|
DBUS_METHOD(dunst_Ping);
|
|
static struct dbus_method methods_dunst[] = {
|
|
{"ContextMenuCall", dbus_cb_dunst_ContextMenuCall},
|
|
{"NotificationAction", dbus_cb_dunst_NotificationAction},
|
|
{"NotificationCloseAll", dbus_cb_dunst_NotificationCloseAll},
|
|
{"NotificationCloseLast", dbus_cb_dunst_NotificationCloseLast},
|
|
{"NotificationShow", dbus_cb_dunst_NotificationShow},
|
|
{"Ping", dbus_cb_dunst_Ping},
|
|
};
|
|
|
|
void dbus_cb_dunst_methods(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *method_name,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation,
|
|
gpointer user_data)
|
|
{
|
|
|
|
struct dbus_method *m = bsearch(method_name,
|
|
methods_dunst,
|
|
G_N_ELEMENTS(methods_dunst),
|
|
sizeof(struct dbus_method),
|
|
cmp_methods);
|
|
|
|
if (m) {
|
|
m->method(connection, sender, parameters, invocation);
|
|
} else {
|
|
LOG_M("Unknown method name: '%s' (sender: '%s').",
|
|
method_name,
|
|
sender);
|
|
}
|
|
}
|
|
|
|
static void dbus_cb_dunst_ContextMenuCall(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
LOG_D("CMD: Calling context menu");
|
|
context_menu();
|
|
|
|
g_dbus_method_invocation_return_value(invocation, NULL);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
static void dbus_cb_dunst_NotificationAction(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
int notification_nr = 0;
|
|
g_variant_get(parameters, "(i)", ¬ification_nr);
|
|
|
|
LOG_D("CMD: Calling action for notification %d", notification_nr);
|
|
|
|
if (notification_nr < 0 || queues_length_waiting() < notification_nr)
|
|
return; //FIXME return error
|
|
|
|
const GList *list = g_list_nth_data(queues_get_displayed(), notification_nr);
|
|
|
|
if (list && list->data) {
|
|
struct notification *n = list->data;
|
|
LOG_D("CMD: Calling action for notification %s", n->summary);
|
|
notification_do_action(n);
|
|
// TODO: do we need to wake up after notification action?
|
|
wake_up();
|
|
}
|
|
|
|
g_dbus_method_invocation_return_value(invocation, NULL);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
static void dbus_cb_dunst_NotificationCloseAll(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
LOG_D("CMD: Pushing all to history");
|
|
queues_history_push_all();
|
|
wake_up();
|
|
|
|
g_dbus_method_invocation_return_value(invocation, NULL);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
static void dbus_cb_dunst_NotificationCloseLast(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
LOG_D("CMD: Closing last notification");
|
|
const GList *list = queues_get_displayed();
|
|
if (list && list->data) {
|
|
struct notification *n = list->data;
|
|
queues_notification_close_id(n->id, REASON_USER);
|
|
wake_up();
|
|
}
|
|
|
|
g_dbus_method_invocation_return_value(invocation, NULL);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
static void dbus_cb_dunst_NotificationShow(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
LOG_D("CMD: Showing last notification from history");
|
|
queues_history_pop();
|
|
wake_up();
|
|
|
|
g_dbus_method_invocation_return_value(invocation, NULL);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
/* Just a simple Ping command to give the ability to dunstctl to test for the existence of this interface
|
|
* Any other way requires parsing the XML of the Introspection or other foo. Just calling the Ping on an old dunst version will fail. */
|
|
static void dbus_cb_dunst_Ping(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
g_dbus_method_invocation_return_value(invocation, NULL);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
|
|
static void dbus_cb_GetCapabilities(
|
|
GDBusConnection *connection,
|
|
const gchar *sender,
|
|
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-hyperlinks");
|
|
|
|
for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i)
|
|
g_variant_builder_add(builder, "s", stack_tag_hints[i]);
|
|
|
|
if (settings.markup != MARKUP_NO)
|
|
g_variant_builder_add(builder, "s", "body-markup");
|
|
|
|
value = g_variant_new("(as)", builder);
|
|
g_clear_pointer(&builder, g_variant_builder_unref);
|
|
g_dbus_method_invocation_return_value(invocation, value);
|
|
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
static struct notification *dbus_message_to_notification(const gchar *sender, GVariant *parameters)
|
|
{
|
|
/* Assert that the parameters' type is actually correct. Albeit usually DBus
|
|
* already rejects ill typed parameters, it may not be always the case. */
|
|
GVariantType *required_type = g_variant_type_new("(susssasa{sv}i)");
|
|
if (!g_variant_is_of_type(parameters, required_type)) {
|
|
g_variant_type_free(required_type);
|
|
return NULL;
|
|
}
|
|
|
|
struct notification *n = notification_create();
|
|
n->dbus_client = g_strdup(sender);
|
|
n->dbus_valid = true;
|
|
|
|
GVariant *hints;
|
|
gchar **actions;
|
|
int timeout;
|
|
|
|
GVariantIter i;
|
|
g_variant_iter_init(&i, parameters);
|
|
|
|
g_variant_iter_next(&i, "s", &n->appname);
|
|
g_variant_iter_next(&i, "u", &n->id);
|
|
g_variant_iter_next(&i, "s", &n->iconname);
|
|
g_variant_iter_next(&i, "s", &n->summary);
|
|
g_variant_iter_next(&i, "s", &n->body);
|
|
g_variant_iter_next(&i, "^a&s", &actions);
|
|
g_variant_iter_next(&i, "@a{?*}", &hints);
|
|
g_variant_iter_next(&i, "i", &timeout);
|
|
|
|
gsize num = 0;
|
|
while (actions[num]) {
|
|
if (actions[num+1]) {
|
|
g_hash_table_insert(n->actions,
|
|
g_strdup(actions[num]),
|
|
g_strdup(actions[num+1]));
|
|
num+=2;
|
|
} else {
|
|
LOG_W("Odd length in actions array. Ignoring element: %s", actions[num]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
GVariant *dict_value;
|
|
if ((dict_value = g_variant_lookup_value(hints, "urgency", G_VARIANT_TYPE_BYTE))) {
|
|
n->urgency = g_variant_get_byte(dict_value);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
if ((dict_value = g_variant_lookup_value(hints, "fgcolor", G_VARIANT_TYPE_STRING))) {
|
|
n->colors.fg = g_variant_dup_string(dict_value, NULL);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
if ((dict_value = g_variant_lookup_value(hints, "bgcolor", G_VARIANT_TYPE_STRING))) {
|
|
n->colors.bg = g_variant_dup_string(dict_value, NULL);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
if ((dict_value = g_variant_lookup_value(hints, "frcolor", G_VARIANT_TYPE_STRING))) {
|
|
n->colors.frame = g_variant_dup_string(dict_value, NULL);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
if ((dict_value = g_variant_lookup_value(hints, "category", G_VARIANT_TYPE_STRING))) {
|
|
n->category = g_variant_dup_string(dict_value, NULL);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
if ((dict_value = g_variant_lookup_value(hints, "desktop-entry", G_VARIANT_TYPE_STRING))) {
|
|
n->desktop_entry = g_variant_dup_string(dict_value, NULL);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
if ((dict_value = g_variant_lookup_value(hints, "image-path", G_VARIANT_TYPE_STRING))) {
|
|
g_free(n->iconname);
|
|
n->iconname = g_variant_dup_string(dict_value, NULL);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
dict_value = g_variant_lookup_value(hints, "image-data", G_VARIANT_TYPE("(iiibiiay)"));
|
|
if (!dict_value)
|
|
dict_value = g_variant_lookup_value(hints, "image_data", G_VARIANT_TYPE("(iiibiiay)"));
|
|
if (!dict_value)
|
|
dict_value = g_variant_lookup_value(hints, "icon_data", G_VARIANT_TYPE("(iiibiiay)"));
|
|
if (dict_value) {
|
|
notification_icon_replace_data(n, dict_value);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
/* Check for transient hints
|
|
*
|
|
* According to the spec, the transient hint should be boolean.
|
|
* But notify-send does not support hints of type 'boolean'.
|
|
* So let's check for int and boolean until notify-send is fixed.
|
|
*/
|
|
if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_BOOLEAN))) {
|
|
n->transient = g_variant_get_boolean(dict_value);
|
|
g_variant_unref(dict_value);
|
|
} else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_UINT32))) {
|
|
n->transient = g_variant_get_uint32(dict_value) > 0;
|
|
g_variant_unref(dict_value);
|
|
} else if ((dict_value = g_variant_lookup_value(hints, "transient", G_VARIANT_TYPE_INT32))) {
|
|
n->transient = g_variant_get_int32(dict_value) > 0;
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_INT32))) {
|
|
n->progress = g_variant_get_int32(dict_value);
|
|
g_variant_unref(dict_value);
|
|
} else if ((dict_value = g_variant_lookup_value(hints, "value", G_VARIANT_TYPE_UINT32))) {
|
|
n->progress = g_variant_get_uint32(dict_value);
|
|
g_variant_unref(dict_value);
|
|
}
|
|
|
|
/* Check for hints that define the stack_tag
|
|
*
|
|
* Only accept to first one we find.
|
|
*/
|
|
for (int i = 0; i < sizeof(stack_tag_hints)/sizeof(*stack_tag_hints); ++i) {
|
|
if ((dict_value = g_variant_lookup_value(hints, stack_tag_hints[i], G_VARIANT_TYPE_STRING))) {
|
|
n->stack_tag = g_variant_dup_string(dict_value, NULL);
|
|
g_variant_unref(dict_value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (timeout >= 0)
|
|
n->timeout = ((gint64)timeout) * 1000;
|
|
|
|
g_variant_unref(hints);
|
|
g_variant_type_free(required_type);
|
|
g_free(actions); // the strv is only a shallow copy
|
|
|
|
notification_init(n);
|
|
return n;
|
|
}
|
|
|
|
static void dbus_cb_Notify(
|
|
GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
struct notification *n = dbus_message_to_notification(sender, parameters);
|
|
if (!n) {
|
|
LOG_W("A notification failed to decode.");
|
|
g_dbus_method_invocation_return_dbus_error(
|
|
invocation,
|
|
FDN_IFAC".Error",
|
|
"Cannot decode notification!");
|
|
return;
|
|
}
|
|
|
|
int id = queues_notification_insert(n);
|
|
|
|
GVariant *reply = g_variant_new("(u)", id);
|
|
g_dbus_method_invocation_return_value(invocation, reply);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
|
|
// The message got discarded
|
|
if (id == 0) {
|
|
signal_notification_closed(n, REASON_USER);
|
|
notification_unref(n);
|
|
}
|
|
|
|
wake_up();
|
|
}
|
|
|
|
static void dbus_cb_CloseNotification(
|
|
GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
guint32 id;
|
|
g_variant_get(parameters, "(u)", &id);
|
|
queues_notification_close_id(id, REASON_SIG);
|
|
wake_up();
|
|
g_dbus_method_invocation_return_value(invocation, NULL);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
static void dbus_cb_GetServerInformation(
|
|
GDBusConnection *connection,
|
|
const gchar *sender,
|
|
GVariant *parameters,
|
|
GDBusMethodInvocation *invocation)
|
|
{
|
|
GVariant *answer = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2");
|
|
|
|
g_dbus_method_invocation_return_value(invocation, answer);
|
|
g_dbus_connection_flush(connection, NULL, NULL, NULL);
|
|
}
|
|
|
|
void signal_notification_closed(struct notification *n, enum reason reason)
|
|
{
|
|
if (!n->dbus_valid) {
|
|
LOG_W("Closing notification '%s' not supported. "
|
|
"Notification already closed.", n->summary);
|
|
return;
|
|
}
|
|
|
|
if (reason < REASON_MIN || REASON_MAX < reason) {
|
|
LOG_W("Closing notification with reason '%d' not supported. "
|
|
"Closing it with reason '%d'.", reason, REASON_UNDEF);
|
|
reason = REASON_UNDEF;
|
|
}
|
|
|
|
if (!dbus_conn) {
|
|
LOG_E("Unable to close notification: No DBus connection.");
|
|
}
|
|
|
|
GVariant *body = g_variant_new("(uu)", n->id, reason);
|
|
GError *err = NULL;
|
|
|
|
g_dbus_connection_emit_signal(dbus_conn,
|
|
n->dbus_client,
|
|
FDN_PATH,
|
|
FDN_IFAC,
|
|
"NotificationClosed",
|
|
body,
|
|
&err);
|
|
|
|
notification_invalidate_actions(n);
|
|
|
|
n->dbus_valid = false;
|
|
|
|
if (err) {
|
|
LOG_W("Unable to close notification: %s", err->message);
|
|
g_error_free(err);
|
|
}
|
|
|
|
}
|
|
|
|
void signal_action_invoked(const struct notification *n, const char *identifier)
|
|
{
|
|
if (!n->dbus_valid) {
|
|
LOG_W("Invoking action '%s' not supported. "
|
|
"Notification already closed.", identifier);
|
|
return;
|
|
}
|
|
|
|
GVariant *body = g_variant_new("(us)", n->id, identifier);
|
|
GError *err = NULL;
|
|
|
|
g_dbus_connection_emit_signal(dbus_conn,
|
|
n->dbus_client,
|
|
FDN_PATH,
|
|
FDN_IFAC,
|
|
"ActionInvoked",
|
|
body,
|
|
&err);
|
|
|
|
if (err) {
|
|
LOG_W("Unable to invoke action: %s", err->message);
|
|
g_error_free(err);
|
|
}
|
|
}
|
|
|
|
//FIXME: Is this necessary or alternative question: Is this implemented correctl?
|
|
// This had been an old relict from the manual times, when I haven't used the
|
|
// interface vtable of GLib
|
|
void dbus_signal_status_changed(struct dunst_status status)
|
|
{
|
|
// We might have not a working connection yet, so just ignore it.
|
|
if (!dbus_conn)
|
|
return;
|
|
|
|
//TODO: I'm pretty sure this is the right format string, but I don't know how to verify it
|
|
GVariantBuilder builder;
|
|
g_variant_builder_init(&builder, G_VARIANT_TYPE("(sa{sv}as)"));
|
|
g_variant_builder_add(&builder, "s", DUNST_IFAC);
|
|
|
|
g_variant_builder_open(&builder, G_VARIANT_TYPE ("a{sv}"));
|
|
g_variant_builder_add(&builder, "{sv}", "running", g_variant_new_boolean(status.running));
|
|
g_variant_builder_close(&builder);
|
|
|
|
g_variant_builder_open(&builder, G_VARIANT_TYPE ("as"));
|
|
g_variant_builder_add(&builder, "s", "unrelated");
|
|
g_variant_builder_close(&builder);
|
|
|
|
GError *err = NULL;
|
|
g_dbus_connection_emit_signal(dbus_conn,
|
|
NULL,
|
|
DUNST_PATH,
|
|
PROPERTIES_IFAC,
|
|
"PropertiesChanged",
|
|
g_variant_builder_end(&builder),
|
|
&err);
|
|
|
|
if (err) {
|
|
LOG_W("Unable send signal 'PropertiesChanged': %s", err->message);
|
|
g_error_free(err);
|
|
}
|
|
}
|
|
|
|
GVariant *dbus_cb_dunst_Properties_Get(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *property_name,
|
|
GError **error,
|
|
gpointer user_data)
|
|
{
|
|
struct dunst_status status = dunst_status_get();
|
|
|
|
if (STR_EQ(property_name, "running"))
|
|
return g_variant_new_boolean(status.running);
|
|
else
|
|
//TODO: is NULL as return value allowed?
|
|
return NULL;
|
|
}
|
|
|
|
gboolean dbus_cb_dunst_Properties_Set(GDBusConnection *connection,
|
|
const gchar *sender,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *property_name,
|
|
GVariant *value,
|
|
GError **error,
|
|
gpointer user_data)
|
|
{
|
|
if (STR_EQ(property_name, "running")) {
|
|
dunst_status(S_RUNNING, g_variant_get_boolean(value));
|
|
wake_up();
|
|
return true;
|
|
}
|
|
|
|
|
|
//FIXME: don't we have to return true on successful setting, but return false, if e.g. the parameter name is wrong?
|
|
//return true;
|
|
// so like this?
|
|
return false;
|
|
}
|
|
|
|
|
|
static const GDBusInterfaceVTable interface_vtable_fdn = {
|
|
dbus_cb_fdn_methods
|
|
};
|
|
|
|
static const GDBusInterfaceVTable interface_vtable_dunst = {
|
|
dbus_cb_dunst_methods,
|
|
dbus_cb_dunst_Properties_Get,
|
|
dbus_cb_dunst_Properties_Set,
|
|
};
|
|
|
|
static void dbus_cb_bus_acquired(GDBusConnection *connection,
|
|
const gchar *name,
|
|
gpointer user_data)
|
|
{
|
|
// TODO: deduplicate the code
|
|
GError *err = NULL;
|
|
if(!g_dbus_connection_register_object(
|
|
connection,
|
|
FDN_PATH,
|
|
introspection_data->interfaces[0],
|
|
&interface_vtable_fdn,
|
|
NULL,
|
|
NULL,
|
|
&err)) {
|
|
DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[0]->name, err->message);
|
|
}
|
|
|
|
if(!g_dbus_connection_register_object(
|
|
connection,
|
|
FDN_PATH,
|
|
introspection_data->interfaces[1],
|
|
&interface_vtable_dunst,
|
|
NULL,
|
|
NULL,
|
|
&err)) {
|
|
DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[1]->name, err->message);
|
|
}
|
|
}
|
|
|
|
static void dbus_cb_name_acquired(GDBusConnection *connection,
|
|
const gchar *name,
|
|
gpointer user_data)
|
|
{
|
|
// If we're not able to get org.fd.N bus, we've still got a problem
|
|
if (STR_EQ(name, FDN_NAME))
|
|
dbus_conn = connection;
|
|
}
|
|
|
|
/**
|
|
* Get the PID of the current process, which acquired FDN DBus Name.
|
|
*
|
|
* If name or vendor specified, the name and vendor
|
|
* will get additionally get via the FDN GetServerInformation method
|
|
*
|
|
* @param connection The DBus connection
|
|
* @param pid The place to report the PID to
|
|
* @param name The place to report the name to, if not required set to NULL
|
|
* @param vendor The place to report the vendor to, if not required set to NULL
|
|
*
|
|
* @retval true: on success
|
|
* @retval false: Any error happened
|
|
*/
|
|
static bool dbus_get_fdn_daemon_info(GDBusConnection *connection,
|
|
guint *pid,
|
|
char **name,
|
|
char **vendor)
|
|
{
|
|
ASSERT_OR_RET(pid, false);
|
|
ASSERT_OR_RET(connection, false);
|
|
|
|
char *owner = NULL;
|
|
GError *error = NULL;
|
|
|
|
GDBusProxy *proxy_fdn;
|
|
GDBusProxy *proxy_dbus;
|
|
|
|
proxy_fdn = g_dbus_proxy_new_sync(
|
|
connection,
|
|
/* do not trigger a start of the notification daemon */
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
|
NULL, /* info */
|
|
FDN_NAME,
|
|
FDN_PATH,
|
|
FDN_IFAC,
|
|
NULL, /* cancelable */
|
|
&error);
|
|
|
|
if (error) {
|
|
g_error_free(error);
|
|
return false;
|
|
}
|
|
|
|
GVariant *daemoninfo = NULL;
|
|
if (name || vendor) {
|
|
daemoninfo = g_dbus_proxy_call_sync(
|
|
proxy_fdn,
|
|
FDN_IFAC ".GetServerInformation",
|
|
NULL,
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
/* It's not worth to wait for the info
|
|
* longer than half a second when dying */
|
|
500,
|
|
NULL, /* cancelable */
|
|
&error);
|
|
}
|
|
|
|
if (error) {
|
|
/* Ignore the error, we may still be able to retrieve the PID */
|
|
g_clear_pointer(&error, g_error_free);
|
|
} else {
|
|
g_variant_get(daemoninfo, "(ssss)", name, vendor, NULL, NULL);
|
|
}
|
|
|
|
owner = g_dbus_proxy_get_name_owner(proxy_fdn);
|
|
|
|
proxy_dbus = g_dbus_proxy_new_sync(
|
|
connection,
|
|
G_DBUS_PROXY_FLAGS_NONE,
|
|
NULL, /* info */
|
|
"org.freedesktop.DBus",
|
|
"/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus",
|
|
NULL, /* cancelable */
|
|
&error);
|
|
|
|
if (error) {
|
|
g_error_free(error);
|
|
return false;
|
|
}
|
|
|
|
GVariant *pidinfo = g_dbus_proxy_call_sync(
|
|
proxy_dbus,
|
|
"org.freedesktop.DBus.GetConnectionUnixProcessID",
|
|
g_variant_new("(s)", owner),
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
/* It's not worth to wait for the PID
|
|
* longer than half a second when dying */
|
|
500,
|
|
NULL,
|
|
&error);
|
|
|
|
if (error) {
|
|
g_error_free(error);
|
|
return false;
|
|
}
|
|
|
|
g_object_unref(proxy_fdn);
|
|
g_object_unref(proxy_dbus);
|
|
g_free(owner);
|
|
if (daemoninfo)
|
|
g_variant_unref(daemoninfo);
|
|
|
|
if (pidinfo) {
|
|
g_variant_get(pidinfo, "(u)", pid);
|
|
g_variant_unref(pidinfo);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
static void dbus_cb_name_lost(GDBusConnection *connection,
|
|
const gchar *name,
|
|
gpointer user_data)
|
|
{
|
|
if (connection) {
|
|
char *name;
|
|
unsigned int pid;
|
|
if (dbus_get_fdn_daemon_info(connection, &pid, &name, NULL)) {
|
|
DIE("Cannot acquire '"FDN_NAME"': "
|
|
"Name is acquired by '%s' with PID '%d'.", name, pid);
|
|
} else {
|
|
DIE("Cannot acquire '"FDN_NAME"'.");
|
|
}
|
|
} else {
|
|
DIE("Cannot connect to DBus.");
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
int dbus_init(void)
|
|
{
|
|
guint owner_id;
|
|
|
|
introspection_data = g_dbus_node_info_new_for_xml(introspection_xml,
|
|
NULL);
|
|
|
|
owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
|
|
FDN_NAME,
|
|
G_BUS_NAME_OWNER_FLAGS_NONE,
|
|
dbus_cb_bus_acquired,
|
|
dbus_cb_name_acquired,
|
|
dbus_cb_name_lost,
|
|
NULL,
|
|
NULL);
|
|
|
|
return owner_id;
|
|
}
|
|
|
|
void dbus_teardown(int owner_id)
|
|
{
|
|
g_clear_pointer(&introspection_data, g_dbus_node_info_unref);
|
|
|
|
g_bus_unown_name(owner_id);
|
|
}
|
|
|
|
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */
|