aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTed Gould <ted@gould.cx>2013-10-28 17:49:04 -0700
committerTed Gould <ted@gould.cx>2013-10-28 17:49:04 -0700
commit4f6b673281fecd22c618c700af0bd798ee5c26f1 (patch)
tree9bc4564892d82786c6e00c446dda58bb100c9e9c
parentfd5fc9ed4aca0753779432de3ccc60cce6499af3 (diff)
parenta440eb1df8296a6b431613ec03aac94db38292c6 (diff)
downloadayatana-indicator-sound-4f6b673281fecd22c618c700af0bd798ee5c26f1.tar.gz
ayatana-indicator-sound-4f6b673281fecd22c618c700af0bd798ee5c26f1.tar.bz2
ayatana-indicator-sound-4f6b673281fecd22c618c700af0bd798ee5c26f1.zip
Merge trunk
-rw-r--r--CMakeLists.txt7
-rw-r--r--data/com.canonical.indicator.sound9
-rw-r--r--debian/changelog101
-rw-r--r--debian/control4
-rw-r--r--po/POTFILES.in3
-rw-r--r--src/CMakeLists.txt13
-rw-r--r--src/Makefile.am.THIS39
-rw-r--r--src/bus-watch-namespace.c349
-rw-r--r--src/bus-watch-namespace.h34
-rw-r--r--src/main.vala2
-rw-r--r--src/media-player-list.vala22
-rw-r--r--src/mpris2-watcher.vala200
-rw-r--r--src/service.vala80
-rw-r--r--src/sound-menu.vala111
-rw-r--r--src/volume-control.vala122
-rw-r--r--vapi/bus-watcher.vapi25
-rw-r--r--vapi/url-dispatcher.vapi9
17 files changed, 822 insertions, 308 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 98dccef..798a0fb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 2.8.9)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")
set(PACKAGE ${CMAKE_PROJECT_NAME})
-set(GETTEXT_PACKAGE ${CMAKE_PROJECT_NAME})
+set(GETTEXT_PACKAGE indicator-sound)
set(GNOMELOCALEDIR "${CMAKE_INSTALL_FULL_DATADIR}/locale")
add_definitions( -DGETTEXT_PACKAGE="${GETTEXT_PACKAGE}" )
@@ -25,11 +25,13 @@ set(SOURCE_BINARY_DIR "${CMAKE_BINARY_DIR}/src")
set(PULSE_AUDIO_REQUIRED_VERSION 0.9.19)
set(GIO_2_0_REQUIRED_VERSION 2.25.13)
+set(URL_DISPATCHER_1_REQUIRED_VERSION 1)
pkg_check_modules(
PULSEAUDIO REQUIRED
libpulse-mainloop-glib>=${PULSE_AUDIO_REQUIRED_VERSION}
- gio-unix-2.0
+ gio-unix-2.0>=${GIO_2_0_REQUIRED_VERSION}
+ url-dispatcher-1>=${URL_DISPATCHER_1_REQUIRED_VERSION}
)
include_directories(${PULSEAUDIO_INCLUDE_DIRS})
@@ -39,6 +41,7 @@ pkg_check_modules(
gio-2.0>=${GIO_2_0_REQUIRED_VERSION}
gio-unix-2.0
libxml-2.0
+ libnotify
)
include_directories(${SOUNDSERVICE_INCLUDE_DIRS})
diff --git a/data/com.canonical.indicator.sound b/data/com.canonical.indicator.sound
index adefae9..ae5fd5d 100644
--- a/data/com.canonical.indicator.sound
+++ b/data/com.canonical.indicator.sound
@@ -10,4 +10,11 @@ ObjectPath=/com/canonical/indicator/sound/desktop
ObjectPath=/com/canonical/indicator/sound/phone
[desktop_greeter]
-ObjectPath=/com/canonical/indicator/sound/desktop
+ObjectPath=/com/canonical/indicator/sound/desktop_greeter
+
+[ubiquity]
+ObjectPath=/com/canonical/indicator/sound/desktop_greeter
+
+[phone_greeter]
+ObjectPath=/com/canonical/indicator/sound/desktop_greeter
+
diff --git a/debian/changelog b/debian/changelog
index 8e259d3..cd9a47e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,104 @@
+indicator-sound (12.10.2+13.10.20131011-0ubuntu2) UNRELEASED; urgency=low
+
+ * Define "ubiquity" indicator profile, reusing the greeter object. (LP:
+ #1241539)
+
+ -- Dmitrijs Ledkovs <xnox@ubuntu.com> Fri, 18 Oct 2013 13:10:03 +0100
+
+indicator-sound (12.10.2+13.10.20131011-0ubuntu1) saucy; urgency=low
+
+ [ Lars Uebernickel ]
+ * Allow activating the 'volume' action It does the same as the
+ 'scroll' action except showing a notification. (LP: #1236292)
+
+ [ Ubuntu daily release ]
+ * Automatic snapshot from revision 389
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 11 Oct 2013 04:27:50 +0000
+
+indicator-sound (12.10.2+13.10.20131004-0ubuntu1) saucy; urgency=low
+
+ [ Pete Woods ]
+ * Fix translation support dh_translations doesn't perform variable
+ substitution when parsing the CMakeLists.txt file. (LP: #1233679)
+
+ [ Ted Gould ]
+ * Add a desktop_greeter menu without settings.
+
+ [ Lars Uebernickel ]
+ * Reconnect when pulseaudio terminates (or crashes). (LP: #1231942)
+ * Show synchronous notification when changing the volume by scrolling
+ over the indicator. (LP: #1225335)
+
+ [ Ubuntu daily release ]
+ * Automatic snapshot from revision 387
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 04 Oct 2013 02:29:38 +0000
+
+indicator-sound (12.10.2+13.10.20130930-0ubuntu1) saucy; urgency=low
+
+ [ Charles Kerr ]
+ * Use url-dispatcher instead of invoking system-settings directly.
+ (LP: #1230819)
+
+ [ Ted Gould ]
+ * Update settings URL to settings:///system. (LP: #1231444)
+
+ [ Ubuntu daily release ]
+ * Automatic snapshot from revision 382
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 30 Sep 2013 06:33:10 +0000
+
+indicator-sound (12.10.2+13.10.20130924-0ubuntu1) saucy; urgency=low
+
+ [ Ted Gould ]
+ * Recommending gnome-control-center first.
+
+ [ Ubuntu daily release ]
+ * Automatic snapshot from revision 379
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 24 Sep 2013 22:45:08 +0000
+
+indicator-sound (12.10.2+13.10.20130918-0ubuntu1) saucy; urgency=low
+
+ [ Lars Uebernickel ]
+ * Don't show the "Mute" menu item in the phone profile. (LP: #1220215)
+
+ [ Ubuntu daily release ]
+ * Automatic snapshot from revision 377
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 18 Sep 2013 06:24:34 +0000
+
+indicator-sound (12.10.2+13.10.20130913-0ubuntu1) saucy; urgency=low
+
+ [ Lars Uebernickel ]
+ * Fixes bug #1221242 and #1204036 (make scrolling and middle clicking
+ work on the sound indicator) It soft-depends on
+ lp:~larsu/libindicator/ng-add-scrolling. That means, this branch can
+ be merged without problems, as it only adds an action and a few
+ attributes on the root item. The bugs won't be fixed until both
+ branches land, though. Please the other merge request for a
+ description of the new attributes. (LP: #1221242, #1204036)
+ * Update POTFILES.in and mark remaining strings as translatable. (LP:
+ #1223500)
+ * Add "title" to the root action state dictionary . (LP: #1223635)
+
+ [ Ubuntu daily release ]
+ * Automatic snapshot from revision 375
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 13 Sep 2013 15:16:35 +0000
+
+indicator-sound (12.10.2+13.10.20130829-0ubuntu1) saucy; urgency=low
+
+ [ Lars Uebernickel ]
+ * Use bus_watch_namespace() for more robust monitoring of mpris
+ players appearing or disappearing on the bus.
+
+ [ Ubuntu daily release ]
+ * Automatic snapshot from revision 371
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 29 Aug 2013 02:08:13 +0000
+
indicator-sound (12.10.2+13.10.20130822-0ubuntu1) saucy; urgency=low
[ Charles Kerr ]
diff --git a/debian/control b/debian/control
index 2c263a3..ff4c041 100644
--- a/debian/control
+++ b/debian/control
@@ -10,8 +10,10 @@ Build-Depends: debhelper (>= 9.0),
autotools-dev,
valac (>= 0.18),
libglib2.0-dev (>= 2.22.3),
+ liburl-dispatcher1-dev,
libpulse-dev (>= 0.9.18),
libpulse-mainloop-glib0 (>= 0.9.18),
+ libnotify-dev,
libgee-dev,
libxml2-dev,
Standards-Version: 3.9.4
@@ -27,7 +29,7 @@ Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
pulseaudio,
-Recommends: ubuntu-system-settings | gnome-control-center,
+Recommends: gnome-control-center | ubuntu-system-settings,
Description: System sound indicator.
System sound indicator which provides easy control of the PulseAudio sound
daemon.
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 39a320f..4bd8cdf 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,4 +1,3 @@
[encoding: UTF-8]
src/service.vala
-src/service.c
-src/sound-menu.c
+src/sound-menu.vala
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ec43b8f..572befd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -14,12 +14,15 @@ vala_init(indicator-sound-service
libxml-2.0
libpulse
libpulse-mainloop-glib
+ libnotify
OPTIONS
--ccode
--thread
--vapidir=${CMAKE_SOURCE_DIR}/vapi/
--vapidir=.
--target-glib=2.36
+ --pkg=url-dispatcher
+ --pkg=bus-watcher
)
vala_add(indicator-sound-service
@@ -47,19 +50,12 @@ vala_add(indicator-sound-service
media-player-list.vala
DEPENDS
media-player
- mpris2-watcher
+ mpris2-interfaces
)
vala_add(indicator-sound-service
mpris2-interfaces.vala
)
vala_add(indicator-sound-service
- mpris2-watcher.vala
- DEPENDS
- media-player
- mpris2-interfaces
- freedesktop-interfaces
-)
-vala_add(indicator-sound-service
freedesktop-interfaces.vala
)
vala_add(indicator-sound-service
@@ -89,6 +85,7 @@ set(
INDICATOR_SOUND_SOURCES
${project_VALA_SOURCES}
${project_VALA_C}
+ bus-watch-namespace.c
${SYMBOLS_PATH}
)
diff --git a/src/Makefile.am.THIS b/src/Makefile.am.THIS
new file mode 100644
index 0000000..1a82a18
--- /dev/null
+++ b/src/Makefile.am.THIS
@@ -0,0 +1,39 @@
+pkglibexec_PROGRAMS = indicator-sound-service
+
+indicator_sound_service_SOURCES = \
+ service.vala \
+ main.vala \
+ volume-control.vala \
+ media-player.vala \
+ media-player-list.vala \
+ mpris2-interfaces.vala \
+ freedesktop-interfaces.vala \
+ sound-menu.vala \
+ bus-watch-namespace.c \
+ bus-watch-namespace.h
+
+indicator_sound_service_VALAFLAGS = \
+ --ccode \
+ --vapidir=$(top_srcdir)/vapi/ \
+ --vapidir=./ \
+ --thread \
+ --pkg config \
+ --pkg gio-2.0 \
+ --pkg gio-unix-2.0 \
+ --pkg libxml-2.0 \
+ --pkg libpulse \
+ --pkg libpulse-mainloop-glib \
+ --pkg bus-watcher \
+ --target-glib=2.36
+
+# -w to disable warnings for vala-generated code
+indicator_sound_service_CFLAGS = $(PULSEAUDIO_CFLAGS) \
+ $(SOUNDSERVICE_CFLAGS) \
+ $(GCONF_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ -DLIBEXECDIR=\"$(libexecdir)\" \
+ -w \
+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\"
+
+indicator_sound_service_LDADD = $(PULSEAUDIO_LIBS) $(SOUNDSERVICE_LIBS) $(GCONF_LIBS)
+indicator_sound_service_LDFLAGS = $(COVERAGE_LDFLAGS)
diff --git a/src/bus-watch-namespace.c b/src/bus-watch-namespace.c
new file mode 100644
index 0000000..f20905c
--- /dev/null
+++ b/src/bus-watch-namespace.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program 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 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#include <gio/gio.h>
+#include <string.h>
+#include "bus-watch-namespace.h"
+
+typedef struct
+{
+ guint id;
+ gchar *name_space;
+ GBusNameAppearedCallback appeared_handler;
+ GBusNameVanishedCallback vanished_handler;
+ gpointer user_data;
+ GDestroyNotify user_data_destroy;
+
+ GDBusConnection *connection;
+ GCancellable *cancellable;
+ GHashTable *names;
+ guint subscription_id;
+} NamespaceWatcher;
+
+typedef struct
+{
+ NamespaceWatcher *watcher;
+ gchar *name;
+} GetNameOwnerData;
+
+static guint namespace_watcher_next_id;
+static GHashTable *namespace_watcher_watchers;
+
+static void
+namespace_watcher_stop (gpointer data)
+{
+ NamespaceWatcher *watcher = data;
+
+ g_cancellable_cancel (watcher->cancellable);
+ g_object_unref (watcher->cancellable);
+
+ if (watcher->subscription_id)
+ g_dbus_connection_signal_unsubscribe (watcher->connection, watcher->subscription_id);
+
+ if (watcher->vanished_handler)
+ {
+ GHashTableIter it;
+ const gchar *name;
+
+ g_hash_table_iter_init (&it, watcher->names);
+ while (g_hash_table_iter_next (&it, (gpointer *) &name, NULL))
+ watcher->vanished_handler (watcher->connection, name, watcher->user_data);
+ }
+
+ if (watcher->user_data_destroy)
+ watcher->user_data_destroy (watcher->user_data);
+
+ if (watcher->connection)
+ {
+ g_signal_handlers_disconnect_by_func (watcher->connection, namespace_watcher_stop, watcher);
+ g_object_unref (watcher->connection);
+ }
+
+ g_hash_table_unref (watcher->names);
+
+ g_hash_table_remove (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id));
+ if (g_hash_table_size (namespace_watcher_watchers) == 0)
+ g_clear_pointer (&namespace_watcher_watchers, g_hash_table_destroy);
+
+ g_free (watcher->name_space);
+
+ g_free (watcher);
+}
+
+static void
+namespace_watcher_name_appeared (NamespaceWatcher *watcher,
+ const gchar *name,
+ const gchar *owner)
+{
+ /* There's a race between NameOwnerChanged signals arriving and the
+ * ListNames/GetNameOwner sequence returning, so this function might
+ * be called more than once for the same name. To ensure that
+ * appeared_handler is only called once for each name, it is only
+ * called when inserting the name into watcher->names (each name is
+ * only inserted once there).
+ */
+ if (g_hash_table_contains (watcher->names, name))
+ return;
+
+ g_hash_table_add (watcher->names, g_strdup (name));
+
+ if (watcher->appeared_handler)
+ watcher->appeared_handler (watcher->connection, name, owner, watcher->user_data);
+}
+
+static void
+namespace_watcher_name_vanished (NamespaceWatcher *watcher,
+ const gchar *name)
+{
+ if (g_hash_table_remove (watcher->names, name) && watcher->vanished_handler)
+ watcher->vanished_handler (watcher->connection, name, watcher->user_data);
+}
+
+static gboolean
+dbus_name_has_namespace (const gchar *name,
+ const gchar *name_space)
+{
+ gint len_name;
+ gint len_namespace;
+
+ len_name = strlen (name);
+ len_namespace = strlen (name_space);
+
+ if (len_name < len_namespace)
+ return FALSE;
+
+ if (memcmp (name_space, name, len_namespace) != 0)
+ return FALSE;
+
+ return len_namespace == len_name || name[len_namespace] == '.';
+}
+
+static void
+name_owner_changed (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ NamespaceWatcher *watcher = user_data;
+ const gchar *name;
+ const gchar *old_owner;
+ const gchar *new_owner;
+
+ g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner);
+
+ if (old_owner[0] != '\0')
+ namespace_watcher_name_vanished (watcher, name);
+
+ if (new_owner[0] != '\0')
+ namespace_watcher_name_appeared (watcher, name, new_owner);
+}
+
+static void
+got_name_owner (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GetNameOwnerData *data = user_data;
+ GError *error = NULL;
+ GVariant *reply;
+ const gchar *owner;
+
+ reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ goto out;
+ }
+
+ if (reply == NULL)
+ {
+ if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
+ g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.GetNameOwner: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_variant_get (reply, "(&s)", &owner);
+ namespace_watcher_name_appeared (data->watcher, data->name, owner);
+
+ g_variant_unref (reply);
+
+out:
+ g_free (data->name);
+ g_slice_free (GetNameOwnerData, data);
+}
+
+static void
+names_listed (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ NamespaceWatcher *watcher;
+ GError *error = NULL;
+ GVariant *reply;
+ GVariantIter *iter;
+ const gchar *name;
+
+ reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ watcher = user_data;
+
+ if (reply == NULL)
+ {
+ g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.ListNames: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ g_variant_get (reply, "(as)", &iter);
+ while (g_variant_iter_next (iter, "&s", &name))
+ {
+ if (dbus_name_has_namespace (name, watcher->name_space))
+ {
+ GetNameOwnerData *data = g_slice_new (GetNameOwnerData);
+ data->watcher = watcher;
+ data->name = g_strdup (name);
+ g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/",
+ "org.freedesktop.DBus", "GetNameOwner",
+ g_variant_new ("(s)", name), G_VARIANT_TYPE ("(s)"),
+ G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable,
+ got_name_owner, data);
+ }
+ }
+
+ g_variant_iter_free (iter);
+ g_variant_unref (reply);
+}
+
+static void
+connection_closed (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error,
+ gpointer user_data)
+{
+ NamespaceWatcher *watcher = user_data;
+
+ namespace_watcher_stop (watcher);
+}
+
+static void
+got_bus (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDBusConnection *connection;
+ NamespaceWatcher *watcher;
+ GError *error = NULL;
+
+ connection = g_bus_get_finish (result, &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_error_free (error);
+ return;
+ }
+
+ watcher = user_data;
+
+ if (connection == NULL)
+ {
+ namespace_watcher_stop (watcher);
+ return;
+ }
+
+ watcher->connection = connection;
+ g_signal_connect (watcher->connection, "closed", G_CALLBACK (connection_closed), watcher);
+
+ watcher->subscription_id =
+ g_dbus_connection_signal_subscribe (watcher->connection, "org.freedesktop.DBus",
+ "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus",
+ watcher->name_space, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE,
+ name_owner_changed, watcher, NULL);
+
+ g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/",
+ "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE ("(as)"),
+ G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable,
+ names_listed, watcher);
+}
+
+guint
+bus_watch_namespace (GBusType bus_type,
+ const gchar *name_space,
+ GBusNameAppearedCallback appeared_handler,
+ GBusNameVanishedCallback vanished_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy)
+{
+ NamespaceWatcher *watcher;
+
+ /* same rules for interfaces and well-known names */
+ g_return_val_if_fail (name_space != NULL && g_dbus_is_interface_name (name_space), 0);
+ g_return_val_if_fail (appeared_handler || vanished_handler, 0);
+
+ watcher = g_new0 (NamespaceWatcher, 1);
+ watcher->id = namespace_watcher_next_id++;
+ watcher->name_space = g_strdup (name_space);
+ watcher->appeared_handler = appeared_handler;
+ watcher->vanished_handler = vanished_handler;
+ watcher->user_data = user_data;
+ watcher->user_data_destroy = user_data_destroy;
+ watcher->cancellable = g_cancellable_new ();
+ watcher->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ if (namespace_watcher_watchers == NULL)
+ namespace_watcher_watchers = g_hash_table_new (g_direct_hash, g_direct_equal);
+ g_hash_table_insert (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id), watcher);
+
+ g_bus_get (bus_type, watcher->cancellable, got_bus, watcher);
+
+ return watcher->id;
+}
+
+void
+bus_unwatch_namespace (guint id)
+{
+ /* namespace_watcher_stop() might have already removed the watcher
+ * with @id in the case of a connection error. Thus, this function
+ * doesn't warn when @id is absent from the hash table.
+ */
+
+ if (namespace_watcher_watchers)
+ {
+ NamespaceWatcher *watcher;
+
+ watcher = g_hash_table_lookup (namespace_watcher_watchers, GUINT_TO_POINTER (id));
+ if (watcher)
+ {
+ /* make sure vanished() is not called as a result of this function */
+ g_hash_table_remove_all (watcher->names);
+
+ namespace_watcher_stop (watcher);
+ }
+ }
+}
diff --git a/src/bus-watch-namespace.h b/src/bus-watch-namespace.h
new file mode 100644
index 0000000..215f6be
--- /dev/null
+++ b/src/bus-watch-namespace.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program 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 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+#ifndef __BUS_WATCH_NAMESPACE_H__
+#define __BUS_WATCH_NAMESPACE_H__
+
+#include <gio/gio.h>
+
+guint bus_watch_namespace (GBusType bus_type,
+ const gchar *name_space,
+ GBusNameAppearedCallback appeared_handler,
+ GBusNameVanishedCallback vanished_handler,
+ gpointer user_data,
+ GDestroyNotify user_data_destroy);
+
+void bus_unwatch_namespace (guint id);
+
+#endif
diff --git a/src/main.vala b/src/main.vala
index 97f311f..4da9e58 100644
--- a/src/main.vala
+++ b/src/main.vala
@@ -7,6 +7,8 @@ static int main (string[] args) {
Intl.setlocale (LocaleCategory.ALL, "");
Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.GNOMELOCALEDIR);
+ Notify.init ("indicator-sound");
+
var service = new IndicatorSound.Service ();
return service.run ();
}
diff --git a/src/media-player-list.vala b/src/media-player-list.vala
index 6eb5fc9..62badc2 100644
--- a/src/media-player-list.vala
+++ b/src/media-player-list.vala
@@ -26,9 +26,7 @@ public class MediaPlayerList {
public MediaPlayerList () {
this._players = new HashTable<string, MediaPlayer> (str_hash, str_equal);
- this.mpris_watcher = new Mpris2Watcher ();
- this.mpris_watcher.client_appeared.connect (this.player_appeared);
- this.mpris_watcher.client_disappeared.connect (this.player_disappeared);
+ BusWatcher.watch_namespace (BusType.SESSION, "org.mpris.MediaPlayer2", this.player_appeared, this.player_disappeared);
}
/* only valid while the list is not changed */
@@ -113,15 +111,21 @@ public class MediaPlayerList {
public signal void player_removed (MediaPlayer player);
HashTable<string, MediaPlayer> _players;
- Mpris2Watcher mpris_watcher;
- void player_appeared (string desktop_id, string dbus_name, bool use_playlists) {
- var player = this.insert (desktop_id);
- if (player != null)
- player.attach (dbus_name);
+ void player_appeared (DBusConnection connection, string name, string owner) {
+ try {
+ MprisRoot mpris2_root = Bus.get_proxy_sync (BusType.SESSION, name, MPRIS_MEDIA_PLAYER_PATH);
+
+ var player = this.insert (mpris2_root.DesktopEntry);
+ if (player != null)
+ player.attach (name);
+ }
+ catch (Error e) {
+ warning ("unable to create mpris proxy for '%s': %s", name, e.message);
+ }
}
- void player_disappeared (string dbus_name) {
+ void player_disappeared (DBusConnection connection, string dbus_name) {
MediaPlayer? player = this._players.find ( (name, player) => {
return player.dbus_name == dbus_name;
});
diff --git a/src/mpris2-watcher.vala b/src/mpris2-watcher.vala
deleted file mode 100644
index 4a1ab6e..0000000
--- a/src/mpris2-watcher.vala
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
-Copyright 2010 Canonical Ltd.
-
-Authors:
- Conor Curran <conor.curran@canonical.com>
-
-This program is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License version 3, as published
-by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranties of
-MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
-PURPOSE. See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along
-with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-using Xml;
-
-public class Mpris2Watcher : GLib.Object
-{
- DBusConnection session_bus;
-
- public signal void client_appeared ( string desktop_file_name,
- string dbus_name,
- bool use_playlists );
- public signal void client_disappeared ( string dbus_name );
-
- public Mpris2Watcher ()
- {
- }
-
- construct
- {
- const string match_rule = "type='signal'," +
- "sender='org.freedesktop.DBus'," +
- "interface='org.freedesktop.DBus'," +
- "member='NameOwnerChanged'," +
- "path='/org/freedesktop/DBus'," +
- "arg0namespace='org.mpris.MediaPlayer2'";
- try {
- this.session_bus = Bus.get_sync (BusType.SESSION);
-
- this.session_bus.call_sync ("org.freedesktop.DBus",
- "/",
- "org.freedesktop.DBus",
- "AddMatch",
- new Variant ("(s)", match_rule),
- VariantType.TUPLE,
- DBusCallFlags.NONE,
- -1);
-
- this.session_bus.signal_subscribe ("org.freedesktop.DBus",
- "org.freedesktop.DBus",
- "NameOwnerChanged",
- "/org/freedesktop/DBus",
- null,
- DBusSignalFlags.NO_MATCH_RULE,
- this.name_owner_changed);
-
- this.check_for_active_clients.begin();
- }
- catch (GLib.Error e) {
- warning ("unable to set up name watch for mrpis clients: %s", e.message);
- }
- }
-
- // At startup check to see if there are clients up that we are interested in
- // More relevant for development and daemon's like mpd.
- async void check_for_active_clients()
- {
- Variant interfaces;
-
- try {
- interfaces = yield this.session_bus.call ("org.freedesktop.DBus",
- "/",
- "org.freedesktop.DBus",
- "ListNames",
- null,
- new VariantType ("(as)"),
- DBusCallFlags.NONE,
- -1);
- }
- catch (GLib.Error e) {
- warning ("unable to search for existing mpris clients: %s ", e.message);
- return;
- }
-
- foreach (var val in interfaces.get_child_value (0)) {
- var address = (string) val;
- if (address.has_prefix (MPRIS_PREFIX)){
- MprisRoot? mpris2_root = this.create_mpris_root(address);
- if (mpris2_root == null) return;
- bool use_playlists = this.supports_playlists ( address );
- client_appeared (mpris2_root.DesktopEntry + ".desktop", address, use_playlists);
- }
- }
- }
-
- void name_owner_changed (DBusConnection con, string sender, string object_path,
- string interface_name, string signal_name, Variant parameters)
- {
- string name, previous_owner, current_owner;
-
- parameters.get ("(sss)", out name, out previous_owner, out current_owner);
-
- MprisRoot? mpris2_root = this.create_mpris_root (name);
- if (mpris2_root == null) return;
-
- if (previous_owner != "" && current_owner == "") {
- debug ("Client '%s' gone down", name);
- client_disappeared (name);
- }
- else if (previous_owner == "" && current_owner != "") {
- debug ("Client '%s' has appeared", name);
- bool use_playlists = this.supports_playlists ( name );
- client_appeared (mpris2_root.DesktopEntry + ".desktop", name, use_playlists);
- }
- }
-
- private MprisRoot? create_mpris_root ( string name ){
- MprisRoot mpris2_root = null;
- if ( name.has_prefix (MPRIS_PREFIX) ){
- try {
- mpris2_root = Bus.get_proxy_sync ( BusType.SESSION,
- name,
- MPRIS_MEDIA_PLAYER_PATH );
- }
- catch (IOError e){
- warning( "Mpris2watcher could not create a root interface: %s",
- e.message );
- }
- }
- return mpris2_root;
- }
-
- private bool supports_playlists ( string name )
- {
- FreeDesktopIntrospectable introspectable;
-
- try {
- /* The dbusproxy flag parameter is needed to ensure Banshee does not
- blow up. I suspect the issue is that if you
- try to instantiate a dbus object which does not have any properties
- associated with it, gdbus will attempt to fetch the properties (this is
- in the documentation) but the banshee mpris dbus object more than likely
- causes a crash because it doesn't check for the presence of properties
- before attempting to access them.
- */
- introspectable = Bus.get_proxy_sync ( BusType.SESSION,
- name,
- MPRIS_MEDIA_PLAYER_PATH,
- GLib.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);
- var results = introspectable.Introspect();
- return this.parse_interfaces (results);
- }
- catch (IOError e){
- warning( "Could not create an introspectable object: %s",
- e.message );
- }
- return false;
- }
-
- private bool parse_interfaces( string interface_info )
- {
- //parse the document from path
- bool result = false;
- Xml.Doc* xml_doc = Parser.parse_doc (interface_info);
- if (xml_doc == null) {
- warning ("Mpris2Watcher - parse-interfaces - failed to instantiate xml doc");
- return false;
- }
- //get the root node. notice the dereferencing operator -> instead of .
- Xml.Node* root_node = xml_doc->get_root_element ();
- if (root_node == null) {
- //free the document manually before throwing because the garbage collector can't work on pointers
- delete xml_doc;
- warning ("Mpris2Watcher - the interface info xml is empty");
- return false;
- }
-
- //let's parse those nodes
- for (Xml.Node* iter = root_node->children; iter != null; iter = iter->next) {
- //spaces btw. tags are also nodes, discard them
- if (iter->type != ElementType.ELEMENT_NODE){
- continue;
- }
- Xml.Attr* attributes = iter->properties; //get the node's name
- string interface_name = attributes->children->content;
- debug ( "this dbus object has interface %s ", interface_name );
- if ( interface_name == MPRIS_PREFIX.concat("Playlists")){
- result = true;
- }
- }
- delete xml_doc;
- return result;
- }
-}
diff --git a/src/service.vala b/src/service.vala
index 4d64502..aa992ff 100644
--- a/src/service.vala
+++ b/src/service.vala
@@ -34,8 +34,9 @@ public class IndicatorSound.Service {
this.actions.add_action (this.create_mic_volume_action ());
this.menus = new HashTable<string, SoundMenu> (str_hash, str_equal);
- this.menus.insert ("desktop", new SoundMenu ("indicator.desktop-settings"));
- this.menus.insert ("phone", new SoundMenu ("indicator.phone-settings"));
+ this.menus.insert ("desktop_greeter", new SoundMenu (null, SoundMenu.DisplayFlags.SHOW_MUTE));
+ this.menus.insert ("desktop", new SoundMenu ("indicator.desktop-settings", SoundMenu.DisplayFlags.SHOW_MUTE));
+ this.menus.insert ("phone", new SoundMenu ("indicator.phone-settings", SoundMenu.DisplayFlags.HIDE_INACTIVE_PLAYERS));
this.menus.@foreach ( (profile, menu) => {
this.volume_control.bind_property ("active-mic", menu, "show-mic-volume", BindingFlags.SYNC_CREATE);
@@ -45,6 +46,14 @@ public class IndicatorSound.Service {
this.settings.changed["interested-media-players"].connect ( () => {
this.players.sync (settings.get_strv ("interested-media-players"));
});
+
+ if (settings.get_boolean ("show-notify-osd-on-scroll")) {
+ unowned List<string> caps = Notify.get_server_caps ();
+ if (caps.find_custom ("x-canonical-private-synchronous", strcmp) != null) {
+ this.notification = new Notify.Notification ("indicator-sound", "", "");
+ this.notification.set_hint_string ("x-canonical-private-synchronous", "indicator-sound");
+ }
+ }
}
public int run () {
@@ -64,6 +73,7 @@ public class IndicatorSound.Service {
const ActionEntry[] action_entries = {
{ "root", null, null, "@a{sv} {}", null },
+ { "scroll", activate_scroll_action, "i", null, null },
{ "desktop-settings", activate_desktop_settings, null, null, null },
{ "phone-settings", activate_phone_settings, null, null, null },
};
@@ -75,13 +85,42 @@ public class IndicatorSound.Service {
VolumeControl volume_control;
MediaPlayerList players;
uint player_action_update_id;
+ Notify.Notification notification;
+
+ const double volume_step_percentage = 0.06;
+
+ void activate_scroll_action (SimpleAction action, Variant? param) {
+ int delta = param.get_int32(); /* positive for up, negative for down */
+
+ double v = this.volume_control.get_volume () + volume_step_percentage * delta;
+ this.volume_control.set_volume (v.clamp (0.0, 1.0));
+
+ if (this.notification != null) {
+ string icon;
+ if (v <= 0.0)
+ icon = "notification-audio-volume-off";
+ else if (v <= 0.3)
+ icon = "notification-audio-volume-low";
+ else if (v <= 0.7)
+ icon = "notification-audio-volume-medium";
+ else
+ icon = "notification-audio-volume-high";
+
+ this.notification.update ("indicator-sound", "", icon);
+ this.notification.set_hint_int32 ("value", ((int32) (100 * v)).clamp (-1, 101));
+ try {
+ this.notification.show ();
+ }
+ catch (Error e) {
+ warning ("unable to show notification: %s", e.message);
+ }
+ }
+ }
void activate_desktop_settings (SimpleAction action, Variant? param) {
var env = Environment.get_variable ("DESKTOP_SESSION");
string cmd;
- if (env == "unity")
- cmd = "gnome-control-center sound-nua";
- else if (env == "xubuntu" || env == "ubuntustudio")
+ if (env == "xubuntu" || env == "ubuntustudio")
cmd = "pavucontrol";
else
cmd = "gnome-control-center sound";
@@ -94,11 +133,7 @@ public class IndicatorSound.Service {
}
void activate_phone_settings (SimpleAction action, Variant? param) {
- try {
- Process.spawn_command_line_async ("system-settings sound");
- } catch (Error e) {
- warning ("unable to launch sound settings: %s", e.message);
- }
+ UrlDispatch.send ("settings:///system/sound");
}
/* Returns a serialized version of @icon_name suited for the panel */
@@ -124,14 +159,15 @@ public class IndicatorSound.Service {
string accessible_name;
if (this.volume_control.mute) {
- accessible_name = "Volume (muted)";
+ accessible_name = _("Volume (muted)");
} else {
int volume_int = (int)(volume * 100);
- accessible_name = @"Volume ($volume_int%)";
+ accessible_name = "%s (%d%%)".printf (_("Volume"), volume_int);
}
var root_action = actions.lookup_action ("root") as SimpleAction;
var builder = new VariantBuilder (new VariantType ("a{sv}"));
+ builder.add ("{sv}", "title", new Variant.string (_("Sound")));
builder.add ("{sv}", "accessible-desc", new Variant.string (accessible_name));
builder.add ("{sv}", "icon", serialize_themed_icon (icon));
builder.add ("{sv}", "visible", new Variant.boolean (true));
@@ -139,10 +175,10 @@ public class IndicatorSound.Service {
}
Action create_mute_action () {
- var mute_action = new SimpleAction.stateful ("mute", null, this.volume_control.mute);
+ var mute_action = new SimpleAction.stateful ("mute", null, new Variant.boolean (this.volume_control.mute));
mute_action.activate.connect ( (action, param) => {
- action.change_state (!action.get_state ().get_boolean ());
+ action.change_state (new Variant.boolean (!action.get_state ().get_boolean ()));
});
mute_action.change_state.connect ( (action, val) => {
@@ -150,7 +186,7 @@ public class IndicatorSound.Service {
});
this.volume_control.notify["mute"].connect ( () => {
- mute_action.set_state (this.volume_control.mute);
+ mute_action.set_state (new Variant.boolean (this.volume_control.mute));
this.update_root_icon ();
});
@@ -159,18 +195,24 @@ public class IndicatorSound.Service {
void volume_changed (double volume) {
var volume_action = this.actions.lookup_action ("volume") as SimpleAction;
- volume_action.set_state (volume);
+ volume_action.set_state (new Variant.double (volume));
this.update_root_icon ();
}
Action create_volume_action () {
- var volume_action = new SimpleAction.stateful ("volume", null, this.volume_control.get_volume ());
+ var volume_action = new SimpleAction.stateful ("volume", VariantType.INT32, new Variant.double (this.volume_control.get_volume ()));
volume_action.change_state.connect ( (action, val) => {
volume_control.set_volume (val.get_double ());
});
+ /* activating this action changes the volume by the amount given in the parameter */
+ volume_action.activate.connect ( (action, param) => {
+ double v = volume_control.get_volume () + volume_step_percentage * param.get_int32 ();
+ volume_control.set_volume (v.clamp (0.0, 1.0));
+ });
+
this.volume_control.volume_changed.connect (volume_changed);
this.volume_control.bind_property ("ready", volume_action, "enabled", BindingFlags.SYNC_CREATE);
@@ -179,14 +221,14 @@ public class IndicatorSound.Service {
}
Action create_mic_volume_action () {
- var volume_action = new SimpleAction.stateful ("mic-volume", null, this.volume_control.get_mic_volume ());
+ var volume_action = new SimpleAction.stateful ("mic-volume", null, new Variant.double (this.volume_control.get_mic_volume ()));
volume_action.change_state.connect ( (action, val) => {
volume_control.set_mic_volume (val.get_double ());
});
this.volume_control.mic_volume_changed.connect ( (volume) => {
- volume_action.set_state (volume);
+ volume_action.set_state (new Variant.double (volume));
});
this.volume_control.bind_property ("ready", volume_action, "enabled", BindingFlags.SYNC_CREATE);
diff --git a/src/sound-menu.vala b/src/sound-menu.vala
index 415a5be..f3f4fd9 100644
--- a/src/sound-menu.vala
+++ b/src/sound-menu.vala
@@ -22,28 +22,44 @@ extern Variant? g_icon_serialize (Icon icon);
class SoundMenu: Object
{
- public SoundMenu (string settings_action) {
+ public enum DisplayFlags {
+ NONE = 0,
+ SHOW_MUTE = 1,
+ HIDE_INACTIVE_PLAYERS = 2
+ }
+
+ public SoundMenu (string? settings_action, DisplayFlags flags) {
/* A sound menu always has at least two sections: the volume section (this.volume_section)
* at the start of the menu, and the settings section at the end. Between those two,
* it has a dynamic amount of player sections, one for each registered player.
*/
this.volume_section = new Menu ();
- volume_section.append (_("Mute"), "indicator.mute");
- volume_section.append_item (this.create_slider_menu_item ("indicator.volume", 0.0, 1.0, 0.01,
+ if ((flags & DisplayFlags.SHOW_MUTE) != 0)
+ volume_section.append (_("Mute"), "indicator.mute");
+ volume_section.append_item (this.create_slider_menu_item ("indicator.volume(0)", 0.0, 1.0, 0.01,
"audio-volume-low-zero-panel",
"audio-volume-high-panel"));
this.menu = new Menu ();
this.menu.append_section (null, volume_section);
- this.menu.append (_("Sound Settingsā€¦"), settings_action);
+
+ if (settings_action != null) {
+ settings_shown = true;
+ this.menu.append (_("Sound Settingsā€¦"), settings_action);
+ }
var root_item = new MenuItem (null, "indicator.root");
root_item.set_attribute ("x-canonical-type", "s", "com.canonical.indicator.root");
+ root_item.set_attribute ("x-canonical-scroll-action", "s", "indicator.scroll");
+ root_item.set_attribute ("x-canonical-secondary-action", "s", "indicator.mute");
root_item.set_submenu (this.menu);
this.root = new Menu ();
root.append_item (root_item);
+
+ this.hide_inactive = (flags & DisplayFlags.HIDE_INACTIVE_PLAYERS) != 0;
+ this.notify_handlers = new HashTable<MediaPlayer, ulong> (direct_hash, direct_equal);
}
public void export (DBusConnection connection, string object_path) {
@@ -56,53 +72,57 @@ class SoundMenu: Object
public bool show_mic_volume {
get {
- return this.volume_section.get_n_items () == 3;
+ return this.mic_volume_shown;
}
set {
- if (value && this.volume_section.get_n_items () < 3) {
+ if (value && !this.mic_volume_shown) {
var slider = this.create_slider_menu_item ("indicator.mic-volume", 0.0, 1.0, 0.01,
"audio-input-microphone-low-zero-panel",
"audio-input-microphone-high-panel");
volume_section.append_item (slider);
+ this.mic_volume_shown = true;
}
- else if (!value && this.volume_section.get_n_items () > 2) {
- this.volume_section.remove (2);
+ else if (!value && this.mic_volume_shown) {
+ this.volume_section.remove (this.volume_section.get_n_items () -1);
+ this.mic_volume_shown = false;
}
}
}
public void add_player (MediaPlayer player) {
- /* Add new players to the end of the player sections, just before the settings */
- var player_item = new MenuItem (player.name, "indicator." + player.id);
- player_item.set_attribute ("x-canonical-type", "s", "com.canonical.unity.media-player");
- player_item.set_attribute_value ("icon", g_icon_serialize (player.icon));
+ if (this.notify_handlers.contains (player))
+ return;
- var playback_item = new MenuItem (null, null);
- playback_item.set_attribute ("x-canonical-type", "s", "com.canonical.unity.playback-item");
- playback_item.set_attribute ("x-canonical-play-action", "s", "indicator.play." + player.id);
- playback_item.set_attribute ("x-canonical-next-action", "s", "indicator.next." + player.id);
- playback_item.set_attribute ("x-canonical-previous-action", "s", "indicator.previous." + player.id);
+ if (player.is_running || !this.hide_inactive)
+ this.insert_player_section (player);
+ this.update_playlists (player);
- var section = new Menu ();
- section.append_item (player_item);
- section.append_item (playback_item);
+ var handler_id = player.notify["is-running"].connect ( () => {
+ if (this.hide_inactive) {
+ if (player.is_running)
+ this.insert_player_section (player);
+ else
+ this.remove_player_section (player);
+ }
+ this.update_playlists (player);
+ });
+ this.notify_handlers.insert (player, handler_id);
player.playlists_changed.connect (this.update_playlists);
- player.notify["is-running"].connect ( () => this.update_playlists (player) );
- update_playlists (player);
-
- this.menu.insert_section (this.menu.get_n_items () -1, null, section);
}
public void remove_player (MediaPlayer player) {
- int index = this.find_player_section (player);
- if (index >= 0)
- this.menu.remove (index);
+ this.remove_player_section (player);
+ this.notify_handlers.remove (player);
}
Menu root;
Menu menu;
Menu volume_section;
+ bool mic_volume_shown;
+ bool settings_shown = false;
+ bool hide_inactive;
+ HashTable<MediaPlayer, ulong> notify_handlers;
/* returns the position in this.menu of the section that's associated with @player */
int find_player_section (MediaPlayer player) {
@@ -119,6 +139,41 @@ class SoundMenu: Object
return -1;
}
+ void insert_player_section (MediaPlayer player) {
+ var section = new Menu ();
+ Icon icon;
+
+ icon = player.icon;
+ if (icon == null)
+ icon = new ThemedIcon.with_default_fallbacks ("application-default-icon");
+
+ var player_item = new MenuItem (player.name, "indicator." + player.id);
+ player_item.set_attribute ("x-canonical-type", "s", "com.canonical.unity.media-player");
+ if (icon != null)
+ player_item.set_attribute_value ("icon", g_icon_serialize (icon));
+ section.append_item (player_item);
+
+ var playback_item = new MenuItem (null, null);
+ playback_item.set_attribute ("x-canonical-type", "s", "com.canonical.unity.playback-item");
+ playback_item.set_attribute ("x-canonical-play-action", "s", "indicator.play." + player.id);
+ playback_item.set_attribute ("x-canonical-next-action", "s", "indicator.next." + player.id);
+ playback_item.set_attribute ("x-canonical-previous-action", "s", "indicator.previous." + player.id);
+ section.append_item (playback_item);
+
+ /* Add new players to the end of the player sections, just before the settings */
+ if (settings_shown) {
+ this.menu.insert_section (this.menu.get_n_items () -1, null, section);
+ } else {
+ this.menu.append_section (null, section);
+ }
+ }
+
+ void remove_player_section (MediaPlayer player) {
+ int index = this.find_player_section (player);
+ if (index >= 0)
+ this.menu.remove (index);
+ }
+
void update_playlists (MediaPlayer player) {
int index = find_player_section (player);
if (index < 0)
@@ -147,7 +202,7 @@ class SoundMenu: Object
var submenu = new Menu ();
submenu.append_section (null, playlists_section);
- player_section.append_submenu ("Choose Playlist", submenu);
+ player_section.append_submenu (_("Choose Playlist"), submenu);
}
MenuItem create_slider_menu_item (string action, double min, double max, double step, string min_icon_name, string max_icon_name) {
diff --git a/src/volume-control.vala b/src/volume-control.vala
index 9475f53..c6db38c 100644
--- a/src/volume-control.vala
+++ b/src/volume-control.vala
@@ -27,6 +27,8 @@ public class VolumeControl : Object
/* this is static to ensure it being freed after @context (loop does not have ref counting) */
private static PulseAudio.GLibMainLoop loop;
+ private uint _reconnect_timer = 0;
+
private PulseAudio.Context context;
private bool _mute = true;
private double _volume = 0.0;
@@ -46,20 +48,13 @@ public class VolumeControl : Object
if (loop == null)
loop = new PulseAudio.GLibMainLoop ();
- var props = new Proplist ();
- props.sets (Proplist.PROP_APPLICATION_NAME, "Ubuntu Audio Settings");
- props.sets (Proplist.PROP_APPLICATION_ID, "com.canonical.settings.sound");
- props.sets (Proplist.PROP_APPLICATION_ICON_NAME, "multimedia-volume-control");
- props.sets (Proplist.PROP_APPLICATION_VERSION, "0.1");
-
- context = new PulseAudio.Context (loop.get_api(), null, props);
-
- context.set_state_callback (context_state_callback);
+ this.reconnect_to_pulse ();
+ }
- if (context.connect(null, Context.Flags.NOFAIL, null) < 0)
- {
- warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno()));
- return;
+ ~VolumeControl ()
+ {
+ if (_reconnect_timer != 0) {
+ Source.remove (_reconnect_timer);
}
}
@@ -133,12 +128,14 @@ public class VolumeControl : Object
context.get_server_info (server_info_cb_for_props);
}
+ private void update_source_get_server_info_cb (PulseAudio.Context c, PulseAudio.ServerInfo? i) {
+ if (i != null)
+ context.get_source_info_by_name (i.default_source_name, source_info_cb);
+ }
+
private void update_source ()
{
- context.get_server_info ( (c, i) => {
- if (i != null)
- context.get_source_info_by_name (i.default_source_name, source_info_cb);
- });
+ context.get_server_info (update_source_get_server_info_cb);
}
private void source_output_info_cb (Context c, SourceOutputInfo? i, int eol)
@@ -153,18 +150,65 @@ public class VolumeControl : Object
private void context_state_callback (Context c)
{
- if (c.get_state () == Context.State.READY)
- {
- c.subscribe (PulseAudio.Context.SubscriptionMask.SINK |
- PulseAudio.Context.SubscriptionMask.SOURCE |
- PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT);
- c.set_subscribe_callback (context_events_cb);
- update_sink ();
- update_source ();
- this.ready = true;
+ switch (c.get_state ()) {
+ case Context.State.READY:
+ c.subscribe (PulseAudio.Context.SubscriptionMask.SINK |
+ PulseAudio.Context.SubscriptionMask.SOURCE |
+ PulseAudio.Context.SubscriptionMask.SOURCE_OUTPUT);
+ c.set_subscribe_callback (context_events_cb);
+ update_sink ();
+ update_source ();
+ this.ready = true;
+ break;
+
+ case Context.State.FAILED:
+ case Context.State.TERMINATED:
+ if (_reconnect_timer == 0)
+ _reconnect_timer = Timeout.add_seconds (2, reconnect_timeout);
+ break;
+
+ default:
+ this.ready = false;
+ break;
}
- else
+ }
+
+ bool reconnect_timeout ()
+ {
+ _reconnect_timer = 0;
+ reconnect_to_pulse ();
+ return false; // G_SOURCE_REMOVE
+ }
+
+ void reconnect_to_pulse ()
+ {
+ if (this.ready) {
+ this.context.disconnect ();
+ this.context = null;
this.ready = false;
+ }
+
+ var props = new Proplist ();
+ props.sets (Proplist.PROP_APPLICATION_NAME, "Ubuntu Audio Settings");
+ props.sets (Proplist.PROP_APPLICATION_ID, "com.canonical.settings.sound");
+ props.sets (Proplist.PROP_APPLICATION_ICON_NAME, "multimedia-volume-control");
+ props.sets (Proplist.PROP_APPLICATION_VERSION, "0.1");
+
+ this.context = new PulseAudio.Context (loop.get_api(), null, props);
+ this.context.set_state_callback (context_state_callback);
+
+ if (context.connect(null, Context.Flags.NOFAIL, null) < 0)
+ warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno()));
+ }
+
+ void sink_info_list_callback_set_mute (PulseAudio.Context context, PulseAudio.SinkInfo? sink, int eol) {
+ if (sink != null)
+ context.set_sink_mute_by_index (sink.index, true, null);
+ }
+
+ void sink_info_list_callback_unset_mute (PulseAudio.Context context, PulseAudio.SinkInfo? sink, int eol) {
+ if (sink != null)
+ context.set_sink_mute_by_index (sink.index, false, null);
}
/* Mute operations */
@@ -172,10 +216,10 @@ public class VolumeControl : Object
{
return_if_fail (context.get_state () == Context.State.READY);
- context.get_sink_info_list ((context, sink, eol) => {
- if (sink != null)
- context.set_sink_mute_by_index (sink.index, mute, null);
- });
+ if (mute)
+ context.get_sink_info_list (sink_info_list_callback_set_mute);
+ else
+ context.get_sink_info_list (sink_info_list_callback_unset_mute);
}
public void toggle_mute ()
@@ -245,19 +289,21 @@ public class VolumeControl : Object
mic_volume_changed (_mic_volume);
}
+ void set_mic_volume_get_server_info_cb (PulseAudio.Context c, PulseAudio.ServerInfo? i) {
+ if (i != null) {
+ unowned CVolume cvol = CVolume ();
+ cvol = vol_set (cvol, 1, double_to_volume (_mic_volume));
+ c.set_source_volume_by_name (i.default_source_name, cvol, set_mic_volume_success_cb);
+ }
+ }
+
public void set_mic_volume (double volume)
{
return_if_fail (context.get_state () == Context.State.READY);
_mic_volume = volume;
- context.get_server_info ( (c, i) => {
- if (i != null) {
- unowned CVolume cvol = CVolume ();
- cvol = vol_set (cvol, 1, double_to_volume (_mic_volume));
- c.set_source_volume_by_name (i.default_source_name, cvol, set_mic_volume_success_cb);
- }
- });
+ context.get_server_info (set_mic_volume_get_server_info_cb);
}
public double get_volume ()
diff --git a/vapi/bus-watcher.vapi b/vapi/bus-watcher.vapi
new file mode 100644
index 0000000..60dacff
--- /dev/null
+++ b/vapi/bus-watcher.vapi
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program 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; version 3.
+ *
+ * This program 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+namespace BusWatcher {
+ [CCode (cheader_filename = "bus-watch-namespace.h", cname = "bus_watch_namespace")]
+ public static uint watch_namespace (GLib.BusType bus_type, string name_space,
+ [CCode (delegate_target_pos = 4.9)] owned GLib.BusNameAppearedCallback? name_appeared,
+ [CCode (delegate_target_pos = 4.9)] owned GLib.BusNameVanishedCallback? name_vanished);
+}
diff --git a/vapi/url-dispatcher.vapi b/vapi/url-dispatcher.vapi
new file mode 100644
index 0000000..88f7740
--- /dev/null
+++ b/vapi/url-dispatcher.vapi
@@ -0,0 +1,9 @@
+[CCode (cprefix="", lower_case_cprefix="", cheader_filename="liburl-dispatcher-1/url-dispatcher.h")]
+
+namespace UrlDispatch
+{
+ public delegate void DispatchCallback ();
+
+ [CCode (cname = "url_dispatch_send")]
+ public static void send (string url, DispatchCallback? func = null);
+}