Add DBus interface to control dunst
This commit is contained in:
parent
0d038021a6
commit
55f4971a92
251
src/dbus.c
251
src/dbus.c
@ -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,7 +69,19 @@ static const char *introspection_xml =
|
||||
" <arg name=\"id\" type=\"u\"/>"
|
||||
" <arg name=\"action_key\" type=\"s\"/>"
|
||||
" </signal>"
|
||||
" </interface>"
|
||||
" </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>";
|
||||
|
||||
static const char *stack_tag_hints[] = {
|
||||
@ -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,12 +134,12 @@ void handle_method_call(GDBusConnection *connection,
|
||||
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);
|
||||
|
||||
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);
|
||||
@ -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;
|
||||
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);
|
||||
}
|
||||
|
||||
registration_id = g_dbus_connection_register_object(connection,
|
||||
FDN_PATH,
|
||||
introspection_data->interfaces[0],
|
||||
&interface_vtable,
|
||||
NULL,
|
||||
NULL,
|
||||
&err);
|
||||
|
||||
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,7 +642,9 @@ static void dbus_cb_name_acquired(GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
dbus_conn = connection;
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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: */
|
||||
|
@ -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 */
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
Loading…
x
Reference in New Issue
Block a user