Add DBus interface to control dunst

This commit is contained in:
Benedikt Heine 2018-09-18 09:19:37 +02:00 committed by Nikos Tsipinakis
parent 0d038021a6
commit 55f4971a92
5 changed files with 252 additions and 28 deletions

View File

@ -8,6 +8,7 @@
#include "dunst.h"
#include "log.h"
#include "menu.h"
#include "notification.h"
#include "queues.h"
#include "settings.h"
@ -17,6 +18,12 @@
#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;
@ -62,6 +69,18 @@ static const char *introspection_xml =
" <arg name=\"id\" type=\"u\"/>"
" <arg name=\"action_key\" type=\"s\"/>"
" </signal>"
" </interface>"
" <interface name=\""DUNST_IFAC"\">"
" <method name=\"ContextMenuCall\" />"
" <method name=\"NotificationCloseLast\" />"
" <method name=\"NotificationCloseAll\" />"
" <method name=\"NotificationShow\" />"
" <property name=\"running\" type=\"b\" access=\"readwrite\">"
" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
" </property>"
" </interface>"
"</node>";
@ -80,6 +99,7 @@ struct dbus_method {
GDBusMethodInvocation *invocation);
};
// TODO: call it interface methods
#define DBUS_METHOD(name) static void dbus_cb_##name( \
GDBusConnection *connection, \
const gchar *sender, \
@ -98,7 +118,6 @@ 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},
@ -106,7 +125,7 @@ static struct dbus_method methods_fdn[] = {
{"Notify", dbus_cb_Notify},
};
void handle_method_call(GDBusConnection *connection,
void dbus_cb_fdn_methods(GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
@ -115,9 +134,9 @@ void handle_method_call(GDBusConnection *connection,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
struct dbus_method *m = bsearch(
method_name,
&methods_fdn,
struct dbus_method *m = bsearch(method_name,
methods_fdn,
G_N_ELEMENTS(methods_fdn),
sizeof(struct dbus_method),
cmp_methods);
@ -131,6 +150,98 @@ void handle_method_call(GDBusConnection *connection,
}
}
DBUS_METHOD(dunst_ContextMenuCall);
DBUS_METHOD(dunst_NotificationCloseAll);
DBUS_METHOD(dunst_NotificationCloseLast);
DBUS_METHOD(dunst_NotificationShow);
static struct dbus_method methods_dunst[] = {
{"ContextMenuCall", dbus_cb_dunst_ContextMenuCall},
{"NotificationCloseAll", dbus_cb_dunst_NotificationCloseAll},
{"NotificationCloseLast", dbus_cb_dunst_NotificationCloseLast},
{"NotificationShow", dbus_cb_dunst_NotificationShow},
};
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_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);
}
static void dbus_cb_GetCapabilities(
GDBusConnection *connection,
const gchar *sender,
@ -347,11 +458,9 @@ static void dbus_cb_GetServerInformation(
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
GVariant *value;
value = g_variant_new("(ssss)", "dunst", "knopwob", VERSION, "1.2");
g_dbus_method_invocation_return_value(invocation, value);
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);
}
@ -420,28 +529,112 @@ void signal_action_invoked(const struct notification *n, const char *identifier)
}
}
static const GDBusInterfaceVTable interface_vtable = {
handle_method_call
//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));
return true;
}
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)
{
guint registration_id;
// TODO: deduplicate the code
GError *err = NULL;
registration_id = g_dbus_connection_register_object(connection,
if(!g_dbus_connection_register_object(
connection,
FDN_PATH,
introspection_data->interfaces[0],
&interface_vtable,
&interface_vtable_fdn,
NULL,
NULL,
&err);
&err)) {
DIE("Unable to register dbus connection interface '%s': %s", introspection_data->interfaces[0]->name, err->message);
}
if (registration_id == 0) {
DIE("Unable to register dbus connection: %s", 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);
}
}
@ -449,6 +642,8 @@ 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;
}

View File

@ -3,6 +3,7 @@
#ifndef DUNST_DBUS_H
#define DUNST_DBUS_H
#include "dunst.h"
#include "notification.h"
/// The reasons according to the notification spec
@ -20,5 +21,12 @@ void dbus_teardown(int id);
void signal_notification_closed(struct notification *n, enum reason reason);
void signal_action_invoked(const struct notification *n, const char *identifier);
/**
* Signal a changed status via DBus
*
* @param status The current status of dunst
* */
void dbus_signal_status_changed(struct dunst_status status);
#endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -31,20 +31,26 @@ static struct dunst_status status;
void dunst_status(const enum dunst_status_field field,
bool value)
{
bool changed;
switch (field) {
case S_FULLSCREEN:
changed = status.fullscreen != value;
status.fullscreen = value;
break;
case S_IDLE:
changed = status.idle != value;
status.idle = value;
break;
case S_RUNNING:
changed = status.running != value;
status.running = value;
break;
default:
LOG_E("Invalid %s enum value in %s:%d", "dunst_status", __FILE__, __LINE__);
break;
}
if (changed)
dbus_signal_status_changed(status);
}
/* see dunst.h */

View File

@ -799,6 +799,12 @@ TEST assert_methodlists_sorted(void)
methods_fdn[i+1].method_name));
}
for (size_t i = 0; i+1 < G_N_ELEMENTS(methods_dunst); i++) {
ASSERT(0 > strcmp(
methods_dunst[i].method_name,
methods_dunst[i+1].method_name));
}
PASS();
}

View File

@ -1,6 +1,15 @@
#define dbus_signal_status_changed(status) signal_sent_stub(status)
#include "../src/dunst.c"
#include "greatest.h"
static bool signal_sent = false;
void signal_sent_stub(struct dunst_status status)
{
signal_sent = true;
return;
}
TEST test_dunst_status(void)
{
status = (struct dunst_status) {false, false, false};