[pulseaudio-discuss] [PATCH] desktop-notifications: Add the initial version of the desktop notifications module.

Ștefan Săftescu stefan.saftescu at gmail.com
Thu Jun 21 18:33:57 PDT 2012


Hello. This is the patch I promised to submit early this week. I took a few days
longer than expected, but here it is.

The code will need some refactoring, as it is currently in one (relatively) big
file. It shouldn't be so difficult once I decide what the general structure of
the module will be. 

There are a few details that need to be sorted out now:

1. What actions should the user be able to invoke on the displayed notification?
   
   My suggestion is "Yes", "No", "Ask me later" (which is equivalent to ignoring
   the notification) as answers to the question "Would you like to set [this
   card] as default?". Do tell me if you have other ideas.

2. How should the module behave when there two cards that have been previously
   set as default? E.g.: the user has already previously set cards A and B as
   default. Currently, the system has card A plugged in and the user inserts
   card B. Should card B set as default? In what circumstances? If the user has
   set A and B as default independently, it's not really possible to determine
   which card the user prefers. However, if the user has set card B as default
   while card A was already plugged in, then the user probably prefers B over A.
   
   This, unfortunately, leads to some overly complicated relationships between
   cards, which would probably require some sort of UI for editing. That is why
   I suggest that, for the moment at least, the module should always set as 
   default the latest plugged in card, so long as the user had already 
   indicated it wants it as default.

3. What is meant by "setting a card as default"?

   My suggestion here is to simply set the first sink and first source of the
   card as default.

Now, I will carry on looking over D-Bus (handling signals or pending calls) and
I will also look over the database functions. I will also write that blog post
after this code gets reviewed.

Let me know what you think.

Ștefan.

---
 configure.ac                               |   19 +++
 src/Makefile.am                            |   14 +-
 src/modules/module-desktop-notifications.c |  206 ++++++++++++++++++++++++++++
 3 files changed, 238 insertions(+), 1 deletion(-)
 create mode 100644 src/modules/module-desktop-notifications.c

diff --git a/configure.ac b/configure.ac
index f5f9b76..6c5813c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1102,6 +1102,23 @@ AC_SUBST(HAVE_SYSTEMD)
 AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$HAVE_SYSTEMD" = x1])
 AS_IF([test "x$HAVE_SYSTEMD" = "x1"], AC_DEFINE([HAVE_SYSTEMD], 1, [Have SYSTEMD?]))
 
+#### Desktop Notifications support (optional, dependant on D-Bus) ####
+
+AC_ARG_ENABLE([desktop-notifications],
+    AS_HELP_STRING([--disable-desktop-notifications],[Disable optional desktop notifications support]))
+
+AS_IF([test "x$enable_desktop_notifications" != "xno"],
+    HAVE_DESKTOP_NOTIFICATIONS=1,
+    HAVE_DESKTOP_NOTIFICATIONS=0)
+
+AS_IF([test "x$HAVE_DBUS" != "x1"], HAVE_DESKTOP_NOTIFICATIONS=0)
+
+AS_IF([test "x$enable_desktop_notifications" != "xyes" && test "x$HAVE_DESKTOP_NOTIFICATIONS" = "x0"],
+    [AC_MSG_ERROR([*** Desktop Notifications support not found (requires D-Bus)])])
+
+AM_CONDITIONAL([HAVE_DESKTOP_NOTIFICATIONS], [test "x$HAVE_DESKTOP_NOTIFICATIONS" = x1])
+
+
 #### Build and Install man pages ####
 
 AC_ARG_ENABLE([manpages],
@@ -1361,6 +1378,7 @@ AS_IF([test "x$HAVE_ORC" = "xyes"], ENABLE_ORC=yes, ENABLE_ORC=no)
 AS_IF([test "x$HAVE_ADRIAN_EC" = "x1"], ENABLE_ADRIAN_EC=yes, ENABLE_ADRIAN_EC=no)
 AS_IF([test "x$HAVE_SPEEX" = "x1"], ENABLE_SPEEX=yes, ENABLE_SPEEX=no)
 AS_IF([test "x$HAVE_WEBRTC" = "x1"], ENABLE_WEBRTC=yes, ENABLE_WEBRTC=no)
+AS_IF([test "x$HAVE_DESKTOP_NOTIFICATIONS" = "x1"], ENABLE_DESKTOP_NOTIFICATIONS=yes, ENABLE_DESKTOP_NOTIFICATIONS=no)
 AS_IF([test "x$HAVE_TDB" = "x1"], ENABLE_TDB=yes, ENABLE_TDB=no)
 AS_IF([test "x$HAVE_GDBM" = "x1"], ENABLE_GDBM=yes, ENABLE_GDBM=no)
 AS_IF([test "x$HAVE_SIMPLEDB" = "x1"], ENABLE_SIMPLEDB=yes, ENABLE_SIMPLEDB=no)
@@ -1412,6 +1430,7 @@ echo "
     Enable Adrian echo canceller:  ${ENABLE_ADRIAN_EC}
     Enable speex (resampler, AEC): ${ENABLE_SPEEX}
     Enable WebRTC echo canceller:  ${ENABLE_WEBRTC}
+    Enable Desktop Notifications:  ${ENABLE_DESKTOP_NOTIFICATIONS}
     Database
       tdb:                         ${ENABLE_TDB}
       gdbm:                        ${ENABLE_GDBM}
diff --git a/src/Makefile.am b/src/Makefile.am
index 127956a..98adacf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1255,6 +1255,11 @@ bin_SCRIPTS += utils/qpaeq
 endif
 endif
 
+if HAVE_DESKTOP_NOTIFICATIONS
+modlibexec_LTLIBRARIES += \
+        module-desktop-notifications.la
+endif
+
 # These are generated by an M4 script
 SYMDEF_FILES = \
 		module-cli-symdef.h \
@@ -1338,7 +1343,8 @@ SYMDEF_FILES = \
 		module-switch-on-connect-symdef.h \
 		module-switch-on-port-available-symdef.h \
 		module-filter-apply-symdef.h \
-		module-filter-heuristics-symdef.h
+		module-filter-heuristics-symdef.h \
+		module-desktop-notifications-symdef.h
 
 if HAVE_ESOUND
 SYMDEF_FILES += \
@@ -1950,6 +1956,12 @@ module_rygel_media_server_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_rygel_media_server_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libprotocol-http.la
 module_rygel_media_server_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
 
+# Desktop Notifications
+module_desktop_notifications_la_SOURCES = modules/module-desktop-notifications.c
+module_desktop_notifications_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_desktop_notifications_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
+module_desktop_notifications_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
+
 ###################################
 #        Some minor stuff         #
 ###################################
diff --git a/src/modules/module-desktop-notifications.c b/src/modules/module-desktop-notifications.c
new file mode 100644
index 0000000..96ab4e5
--- /dev/null
+++ b/src/modules/module-desktop-notifications.c
@@ -0,0 +1,206 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2012 Ștefan Săftescu
+
+  PulseAudio 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.
+
+  PulseAudio 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; 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
+
+#include <dbus/dbus.h>
+
+#include <pulse/proplist.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/card.h>
+#include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-shared.h>
+#include <pulsecore/dbus-util.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/module.h>
+
+#include "module-desktop-notifications-symdef.h"
+
+PA_MODULE_AUTHOR("Ștefan Săftescu");
+PA_MODULE_DESCRIPTION("Integration with the Desktop Notifications specification.");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+
+typedef struct pa_notification pa_notification;
+
+struct userdata {
+    pa_hook_slot *card_put_slot;
+};
+
+struct pa_notification {
+    char *app_name;
+    dbus_uint32_t replaces_id;
+    char *app_icon;
+    char *summary;
+    char *body;
+    char **actions;
+    size_t num_actions;
+    dbus_int32_t expire_timeout;
+};
+
+static pa_hook_result_t card_put_cb(pa_core *c, pa_card *card, void* userdata);
+static pa_dbus_pending* show_notification(DBusConnection *conn, pa_notification *n);
+
+pa_notification* pa_notification_new(size_t num_actions);
+void pa_notification_free(pa_notification *n);
+
+void pa_dbus_append_basic_variant_dict(DBusMessageIter *iter, const char** keys, int item_type, const void *data, unsigned n);
+
+int pa__init(pa_module*m) {
+    struct userdata *u;
+
+    m->userdata = u = pa_xnew(struct userdata, 1);
+
+    u->card_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_LATE, (pa_hook_cb_t) card_put_cb, u);
+
+    return 0;
+}
+
+void pa__done(pa_module*m) {
+    struct userdata *u;
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->card_put_slot)
+        pa_hook_slot_free(u->card_put_slot);
+
+    pa_xfree(u);
+}
+
+static pa_hook_result_t card_put_cb(pa_core *c, pa_card *card, void *userdata) {
+    char *card_name;
+    DBusError err;
+    pa_dbus_connection *conn;
+    pa_notification *n;
+
+    card_name = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION);
+    pa_log_debug("Card detected: %s.", card_name);
+
+
+    dbus_error_init(&err);
+    conn = pa_dbus_bus_get(c, DBUS_BUS_SESSION, &err);
+
+    n = pa_notification_new(0);
+    n->summary = "A new card has been detected.";
+    n->body = pa_sprintf_malloc("%s has been detected. Would you like to set it as default?", card_name);
+
+    show_notification(pa_dbus_connection_get(conn), n);
+
+    pa_notification_free(n);
+    pa_dbus_connection_unref(conn);
+    dbus_error_free(&err);
+
+    return PA_HOOK_OK;
+}
+
+static pa_dbus_pending* show_notification(DBusConnection *conn, pa_notification *n) {
+    DBusMessage* msg;
+    DBusMessageIter args;
+    DBusPendingCall* pending;
+
+    msg = dbus_message_new_method_call(
+            "org.freedesktop.Notifications",
+            "/org/freedesktop/Notifications",
+            "org.freedesktop.Notifications",
+            "Notify"
+    );
+
+    /* FIXME: free memory when there's an error? */
+    pa_assert_se(NULL != msg);
+
+    dbus_message_iter_init_append(msg, &args);
+
+    pa_assert_se(dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, (void *) &n->app_name));
+    pa_assert_se(dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, (void *) &n->replaces_id));
+    pa_assert_se(dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, (void *) &n->app_icon));
+    pa_assert_se(dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, (void *) &n->summary));
+    pa_assert_se(dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, (void *) &n->body));
+
+    pa_dbus_append_basic_array(&args, DBUS_TYPE_STRING, (void *) n->actions, n->num_actions);
+    pa_dbus_append_basic_variant_dict(&args, NULL, DBUS_TYPE_STRING, NULL, 0);
+
+    pa_assert_se(dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, (void *) &n->expire_timeout));
+
+
+    pa_assert_se(dbus_connection_send_with_reply (conn, msg, &pending, -1));
+    pa_assert_se(pending != NULL);
+
+    dbus_connection_flush(conn);
+
+    return pa_dbus_pending_new(conn, msg, pending, NULL, NULL);
+}
+
+pa_notification* pa_notification_new(size_t num_actions) {
+    pa_notification *n;
+
+    n = pa_xnew(pa_notification, 1);
+
+    n->app_name = "PulseAudio";
+    n->app_icon = "";
+
+    if(num_actions > 0)
+        n->actions = pa_xnew(char*, num_actions);
+
+    n->num_actions = num_actions;
+    n->replaces_id = 0;
+    n->expire_timeout = -1;
+
+    return n;
+}
+
+void pa_notification_free(pa_notification *n) {
+    pa_xfree(n->actions);
+    pa_xfree(n);
+}
+
+/* this is probably not that useful, since you would want to use variants in
+       dicts to have different types */
+void pa_dbus_append_basic_variant_dict(
+        DBusMessageIter *iter,
+        const char** keys,
+        int item_type,
+        const void *array,
+        unsigned n) {
+
+    DBusMessageIter dict_iter;
+    unsigned i;
+    unsigned item_size;
+
+    pa_assert(iter);
+    pa_assert(dbus_type_is_basic(item_type));
+    pa_assert((keys && array) || n == 0);
+
+    item_size = sizeof(char*); /*basic_type_size(item_type);*/
+
+    pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+    for(i = 0; i < n; ++i)
+        pa_dbus_append_basic_variant_dict_entry(&dict_iter, keys[i], item_type, &((uint8_t*) array)[i * item_size]);
+
+    pa_assert_se(dbus_message_iter_close_container(iter, &dict_iter));
+}
-- 
1.7.10.2



More information about the pulseaudio-discuss mailing list