aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTed Gould <ted@gould.cx>2014-03-24 15:02:25 +0000
committerCI bot <ps-jenkins@lists.canonical.com>2014-03-24 15:02:25 +0000
commit7a312f2f03a0bc0c52ce8a6f834849d70233cf61 (patch)
treeb7e124f7b3297dc0ea03e08052ae63a7d0d0acc6
parent0aae819193bfd6dba02207c4513ccb038d412d4e (diff)
parent7d47058ef00b8aab6e7b58e20bd6e5fbcecdfc34 (diff)
downloadayatana-indicator-sound-7a312f2f03a0bc0c52ce8a6f834849d70233cf61.tar.gz
ayatana-indicator-sound-7a312f2f03a0bc0c52ce8a6f834849d70233cf61.tar.bz2
ayatana-indicator-sound-7a312f2f03a0bc0c52ce8a6f834849d70233cf61.zip
Export currently running player to Accounts Service
-rw-r--r--.bzrignore10
-rw-r--r--CMakeLists.txt8
-rw-r--r--data/50-com.canonical.indicator.sound.AccountsService.pkla6
-rw-r--r--data/CMakeLists.txt32
-rw-r--r--data/com.canonical.indicator.sound.AccountsService.policy23
-rw-r--r--data/com.canonical.indicator.sound.AccountsService.xml42
-rw-r--r--data/com.canonical.indicator.sound.gschema.xml9
-rw-r--r--debian/control6
-rwxr-xr-xdebian/rules5
-rw-r--r--src/CMakeLists.txt61
-rw-r--r--src/accounts-service-sound-settings.vala33
-rw-r--r--src/accounts-service-user.vala135
-rw-r--r--src/main.c32
-rw-r--r--src/main.vala14
-rw-r--r--src/media-player-list.vala20
-rw-r--r--src/media-player-mpris.vala299
-rw-r--r--src/media-player.vala297
-rw-r--r--src/service.vala60
-rw-r--r--src/volume-control.vala1
-rw-r--r--tests/CMakeLists.txt94
-rw-r--r--tests/accounts-service-mock.h90
-rw-r--r--tests/accounts-service-user.cc201
-rw-r--r--tests/media-player-mock.vala76
23 files changed, 1246 insertions, 308 deletions
diff --git a/.bzrignore b/.bzrignore
index 166a1ca..d010156 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -13,3 +13,13 @@
/src/service.c
/src/volume-control.c
/src/freedesktop-interfaces.c
+
+# CMake Generated Files
+CMakeFiles/
+Makefile
+cmake_install.cmake
+CMakeCache.txt
+CTestTestfile.cmake
+config.h
+
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ecd2f12..8c8d69c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -40,11 +40,19 @@ pkg_check_modules(
gee-1.0
gio-2.0>=${GIO_2_0_REQUIRED_VERSION}
gio-unix-2.0
+ gthread-2.0
libxml-2.0
libnotify
+ accountsservice
)
include_directories(${SOUNDSERVICE_INCLUDE_DIRS})
+pkg_check_modules(
+ TEST REQUIRED
+ dbustest-1
+)
+include_directories(${TEST_INCLUDE_DIRS})
+
find_package(Vala 0.20)
find_package(GObjectIntrospection 0.9.12)
diff --git a/data/50-com.canonical.indicator.sound.AccountsService.pkla b/data/50-com.canonical.indicator.sound.AccountsService.pkla
new file mode 100644
index 0000000..bbcca1e
--- /dev/null
+++ b/data/50-com.canonical.indicator.sound.AccountsService.pkla
@@ -0,0 +1,6 @@
+[Allow LightDM to set Unity AccountsService fields]
+Identity=unix-user:lightdm
+Action=com.canonical.indicator.sound.AccountsService.ModifyAnyUser
+ResultActive=yes
+ResultInactive=yes
+ResultAny=yes
diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt
index 80046f7..955a4a4 100644
--- a/data/CMakeLists.txt
+++ b/data/CMakeLists.txt
@@ -82,3 +82,35 @@ install(
###########################
add_schema ("com.canonical.indicator.sound.gschema.xml")
+
+###########################
+# Accounts Service
+###########################
+
+
+set(POLKIT_LIB_DIR "${CMAKE_INSTALL_LOCALSTATEDIR}/lib/polkit-1")
+set(POLKIT_DATA_DIR "${CMAKE_INSTALL_PREFIX}/share/polkit-1")
+set(DBUS_IFACE_DIR "${CMAKE_INSTALL_PREFIX}/share/dbus-1/interfaces")
+set(ACCOUNTS_IFACE_DIR "${CMAKE_INSTALL_PREFIX}/share/accountsservice/interfaces")
+
+install(FILES com.canonical.indicator.sound.AccountsService.xml
+ DESTINATION "${DBUS_IFACE_DIR}"
+)
+
+# Create accountsservice symlink for above dbus interface
+install(CODE "
+ execute_process(COMMAND mkdir -p \"\$ENV{DESTDIR}${ACCOUNTS_IFACE_DIR}\")
+ execute_process(COMMAND ln -sf ../../dbus-1/interfaces/com.canonical.indicator.sound.AccountsService.xml \"\$ENV{DESTDIR}${ACCOUNTS_IFACE_DIR}\")
+")
+
+install(FILES com.canonical.indicator.sound.AccountsService.policy
+DESTINATION "${POLKIT_DATA_DIR}/actions"
+)
+
+install(FILES 50-com.canonical.indicator.sound.AccountsService.pkla
+DESTINATION "${POLKIT_LIB_DIR}/localauthority/10-vendor.d"
+)
+
+
+
+
diff --git a/data/com.canonical.indicator.sound.AccountsService.policy b/data/com.canonical.indicator.sound.AccountsService.policy
new file mode 100644
index 0000000..4d0ee75
--- /dev/null
+++ b/data/com.canonical.indicator.sound.AccountsService.policy
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<policyconfig>
+ <action id="com.canonical.indicator.sound.AccountsService.ModifyOwnUser">
+ <description>Set properties of own user</description>
+ <message>Authentication is required to set one's own indicator sound properties.</message>
+ <defaults>
+ <allow_any>yes</allow_any>
+ <allow_inactive>yes</allow_inactive>
+ <allow_active>yes</allow_active>
+ </defaults>
+ </action>
+
+ <action id="com.canonical.indicator.sound.AccountsService.ModifyAnyUser">
+ <description>Set properties of any user</description>
+ <message>Authentication is required to set another user's indicator sound properties.</message>
+ <defaults>
+ <allow_any>no</allow_any>
+ <allow_inactive>no</allow_inactive>
+ <allow_active>no</allow_active>
+ </defaults>
+ </action>
+</policyconfig>
diff --git a/data/com.canonical.indicator.sound.AccountsService.xml b/data/com.canonical.indicator.sound.AccountsService.xml
new file mode 100644
index 0000000..fb7e96f
--- /dev/null
+++ b/data/com.canonical.indicator.sound.AccountsService.xml
@@ -0,0 +1,42 @@
+<node>
+ <interface name="com.canonical.indicator.sound.AccountsService">
+
+ <annotation name="org.freedesktop.Accounts.VendorExtension" value="true"/>
+
+ <annotation name="org.freedesktop.Accounts.Authentication.ChangeOwn"
+ value="com.canonical.indicator.sound.AccountsService.ModifyOwnUser"/>
+
+ <annotation name="org.freedesktop.Accounts.Authentication.ReadAny"
+ value="com.canonical.indicator.sound.AccountsService.ModifyAnyUser"/>
+
+ <annotation name="org.freedesktop.Accounts.Authentication.ChangeAny"
+ value="com.canonical.indicator.sound.AccountsService.ModifyAnyUser"/>
+
+ <property name="Timestamp" type="t" access="readwrite">
+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
+ </property>
+ <property name="PlayerName" type="s" access="readwrite">
+ <annotation name="org.freedesktop.Accounts.DefaultValue" value=""/>
+ </property>
+ <property name="PlayerIcon" type="v" access="readwrite"/>
+ <property name="Running" type="b" access="readwrite">
+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="false"/>
+ </property>
+ <property name="State" type="s" access="readwrite">
+ <annotation name="org.freedesktop.Accounts.DefaultValue" value=""/>
+ </property>
+ <property name="Title" type="s" access="readwrite">
+ <annotation name="org.freedesktop.Accounts.DefaultValue" value=""/>
+ </property>
+ <property name="Artist" type="s" access="readwrite">
+ <annotation name="org.freedesktop.Accounts.DefaultValue" value=""/>
+ </property>
+ <property name="Album" type="s" access="readwrite">
+ <annotation name="org.freedesktop.Accounts.DefaultValue" value=""/>
+ </property>
+ <property name="ArtUrl" type="s" access="readwrite">
+ <annotation name="org.freedesktop.Accounts.DefaultValue" value=""/>
+ </property>
+
+ </interface>
+</node>
diff --git a/data/com.canonical.indicator.sound.gschema.xml b/data/com.canonical.indicator.sound.gschema.xml
index 102a1db..c34dfd6 100644
--- a/data/com.canonical.indicator.sound.gschema.xml
+++ b/data/com.canonical.indicator.sound.gschema.xml
@@ -48,5 +48,14 @@
Whether or not to show the sound indicator in the menu bar.
</description>
</key>
+ <key name="greeter-export" type="b">
+ <default>true</default>
+ <summary>Whether or not to export the currently playing song to the greeter.</summary>
+ <description>
+ If enabled the sound indicator will export the current player and
+ song to the greeter so that it can be shown if the user is selected
+ and the sound menu is shown.
+ </description>
+ </key>
</schema>
</schemalist>
diff --git a/debian/control b/debian/control
index c7b3ed8..4278f7a 100644
--- a/debian/control
+++ b/debian/control
@@ -6,10 +6,15 @@ XSBC-Original-Maintainer: Conor Curran <conor.curran@canonical.com>
Build-Depends: debhelper (>= 9.0),
cmake,
dbus,
+ dbus-test-runner (>= 14.04.0+14.04.20140226),
dh-translations,
+ gir1.2-accountsservice-1.0,
gnome-common,
autotools-dev,
valac (>= 0.20),
+ libaccountsservice-dev,
+ libdbustest1-dev,
+ libgirepository1.0-dev,
libglib2.0-dev (>= 2.22.3),
libgtest-dev,
liburl-dispatcher1-dev,
@@ -18,6 +23,7 @@ Build-Depends: debhelper (>= 9.0),
libnotify-dev,
libgee-dev,
libxml2-dev,
+ python3-dbusmock,
Standards-Version: 3.9.4
Homepage: https://launchpad.net/indicator-sound
# If you aren't a member of ~indicator-applet-developers but need to upload
diff --git a/debian/rules b/debian/rules
index 6df5436..8fb0f91 100755
--- a/debian/rules
+++ b/debian/rules
@@ -5,6 +5,11 @@ export DPKG_GENSYMBOLS_CHECK_LEVEL=4
%:
dh $@ --parallel --fail-missing --with translations
+override_dh_auto_configure:
+ # Debian defines CMAKE_INSTALL_LOCALSTATEDIR as /usr/var, which is wrong.
+ # So until Debian bug 719148 is fixed, do it ourselves.
+ dh_auto_configure -- -DCMAKE_INSTALL_LOCALSTATEDIR="/var"
+
override_dh_install:
mkdir -p debian/indicator-sound/usr/share/apport/package-hooks/
install -m 644 debian/source_indicator-sound.py debian/indicator-sound/usr/share/apport/package-hooks/
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c11ec51..280e89a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,8 +5,20 @@
set(HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.h")
set(SYMBOLS_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.def")
+set(VAPI_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.vapi")
+
+vapi_gen(accounts-service
+ LIBRARY
+ accounts-service
+ PACKAGES
+ gio-2.0
+ INPUT
+ /usr/share/gir-1.0/AccountsService-1.0.gir
+)
vala_init(indicator-sound-service
+ DEPENDS
+ accounts-service
PACKAGES
config
gio-2.0
@@ -15,12 +27,12 @@ vala_init(indicator-sound-service
libpulse
libpulse-mainloop-glib
libnotify
+ accounts-service
OPTIONS
--ccode
--thread
--vapidir=${CMAKE_SOURCE_DIR}/vapi/
--vapidir=.
- --target-glib=2.36
--pkg=url-dispatcher
--pkg=bus-watcher
)
@@ -33,24 +45,25 @@ vala_add(indicator-sound-service
media-player
media-player-list
mpris2-interfaces
-)
-vala_add(indicator-sound-service
- main.vala
- DEPENDS
- service
+ accounts-service-user
)
vala_add(indicator-sound-service
volume-control.vala
)
vala_add(indicator-sound-service
media-player.vala
+)
+vala_add(indicator-sound-service
+ media-player-mpris.vala
DEPENDS
+ media-player
mpris2-interfaces
)
vala_add(indicator-sound-service
media-player-list.vala
DEPENDS
media-player
+ media-player-mpris
mpris2-interfaces
)
vala_add(indicator-sound-service
@@ -65,6 +78,16 @@ vala_add(indicator-sound-service
media-player
mpris2-interfaces
)
+vala_add(indicator-sound-service
+ accounts-service-user.vala
+ DEPENDS
+ media-player
+ mpris2-interfaces
+ accounts-service-sound-settings
+)
+vala_add(indicator-sound-service
+ accounts-service-sound-settings.vala
+)
vala_finish(indicator-sound-service
SOURCES
@@ -75,6 +98,8 @@ vala_finish(indicator-sound-service
${HEADER_PATH}
GENERATE_SYMBOLS
${SYMBOLS_PATH}
+ GENERATE_VAPI
+ ${VAPI_PATH}
)
set_source_files_properties(
@@ -92,16 +117,33 @@ set(
)
###########################
-# Executable
+# Lib
###########################
add_definitions(
-w
)
+add_library(
+ indicator-sound-service-lib STATIC
+ ${INDICATOR_SOUND_SOURCES}
+)
+
+target_link_libraries(
+ indicator-sound-service-lib
+ ${PULSEAUDIO_LIBRARIES}
+ ${SOUNDSERVICE_LIBRARIES}
+)
+
+###########################
+# Executable
+###########################
+
+include_directories(${CMAKE_BINARY_DIR})
+
add_executable(
indicator-sound-service-bin
- ${INDICATOR_SOUND_SOURCES}
+ main.c
)
set_target_properties(
@@ -112,8 +154,7 @@ set_target_properties(
target_link_libraries(
indicator-sound-service-bin
- ${PULSEAUDIO_LIBRARIES}
- ${SOUNDSERVICE_LIBRARIES}
+ indicator-sound-service-lib
)
###########################
diff --git a/src/accounts-service-sound-settings.vala b/src/accounts-service-sound-settings.vala
new file mode 100644
index 0000000..7e27bd5
--- /dev/null
+++ b/src/accounts-service-sound-settings.vala
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 © Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ */
+
+[DBus (name = "com.canonical.indicator.sound.AccountsService")]
+public interface AccountsServiceSoundSettings : Object {
+ // properties
+ public abstract uint64 timestamp {owned get; set;}
+ public abstract string player_name {owned get; set;}
+ public abstract Variant player_icon {owned get; set;}
+ public abstract bool running {owned get; set;}
+ public abstract string state {owned get; set;}
+ public abstract string title {owned get; set;}
+ public abstract string artist {owned get; set;}
+ public abstract string album {owned get; set;}
+ public abstract string art_url {owned get; set;}
+}
+
diff --git a/src/accounts-service-user.vala b/src/accounts-service-user.vala
new file mode 100644
index 0000000..c29842a
--- /dev/null
+++ b/src/accounts-service-user.vala
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2014 © Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ */
+
+public class AccountsServiceUser : Object {
+ Act.UserManager accounts_manager = Act.UserManager.get_default();
+ Act.User? user = null;
+ AccountsServiceSoundSettings? proxy = null;
+ uint timer = 0;
+ MediaPlayer? _player = null;
+
+ public MediaPlayer? player {
+ set {
+ this._player = value;
+ debug("New player: %s", this._player != null ? this._player.name : "Cleared");
+
+ /* No proxy, no settings to set */
+ if (this.proxy == null) {
+ debug("Nothing written to Accounts Service, waiting on proxy");
+ return;
+ }
+
+ /* Always reset the timer */
+ if (this.timer != 0) {
+ GLib.Source.remove(this.timer);
+ this.timer = 0;
+ }
+
+ if (this._player == null) {
+ debug("Clearing player data in accounts service");
+
+ /* Clear it */
+ this.proxy.player_name = "";
+ this.proxy.timestamp = 0;
+ this.proxy.title = "";
+ this.proxy.artist = "";
+ this.proxy.album = "";
+ this.proxy.art_url = "";
+
+ var icon = new ThemedIcon.with_default_fallbacks ("application-default-icon");
+ this.proxy.player_icon = icon.serialize();
+ } else {
+ this.proxy.timestamp = GLib.get_monotonic_time();
+ this.proxy.player_name = this._player.name;
+ if (this._player.icon == null) {
+ var icon = new ThemedIcon.with_default_fallbacks ("application-default-icon");
+ this.proxy.player_icon = icon.serialize();
+ } else {
+ this.proxy.player_icon = this._player.icon.serialize();
+ }
+
+ this.proxy.running = this._player.is_running;
+ this.proxy.state = this._player.state;
+
+ if (this._player.current_track != null) {
+ this.proxy.title = this._player.current_track.title;
+ this.proxy.artist = this._player.current_track.artist;
+ this.proxy.album = this._player.current_track.album;
+ this.proxy.art_url = this._player.current_track.art_url;
+ } else {
+ this.proxy.title = "";
+ this.proxy.artist = "";
+ this.proxy.album = "";
+ this.proxy.art_url = "";
+ }
+
+ this.timer = GLib.Timeout.add_seconds(5 * 60, () => {
+ debug("Writing timestamp");
+ this.proxy.timestamp = GLib.get_monotonic_time();
+ return true;
+ });
+ }
+ }
+ get {
+ return this._player;
+ }
+ }
+
+ public AccountsServiceUser () {
+ user = accounts_manager.get_user(GLib.Environment.get_user_name());
+ user.notify["is-loaded"].connect(() => user_loaded_changed());
+ user_loaded_changed();
+ }
+
+ void user_loaded_changed () {
+ debug("User loaded changed");
+
+ this.proxy = null;
+
+ if (this.user.is_loaded) {
+ Bus.get_proxy.begin<AccountsServiceSoundSettings> (
+ BusType.SYSTEM,
+ "org.freedesktop.Accounts",
+ user.get_object_path(),
+ DBusProxyFlags.GET_INVALIDATED_PROPERTIES,
+ null,
+ new_proxy);
+ }
+ }
+
+ ~AccountsServiceUser () {
+ debug("Account Service Object Finalizing");
+ this.player = null;
+
+ if (this.timer != 0) {
+ GLib.Source.remove(this.timer);
+ this.timer = 0;
+ }
+ }
+
+ void new_proxy (GLib.Object? obj, AsyncResult res) {
+ try {
+ this.proxy = Bus.get_proxy.end (res);
+ this.player = _player;
+ } catch (Error e) {
+ this.proxy = null;
+ warning("Unable to get proxy to user sound settings: %s", e.message);
+ }
+ }
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..f8635c8
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,32 @@
+/* main.c generated by valac 0.22.1, the Vala compiler
+ * generated from main.vala, do not modify */
+
+
+#include <glib.h>
+#include <locale.h>
+#include <libnotify/notify.h>
+
+#include "indicator-sound-service.h"
+#include "config.h"
+
+int
+main (int argc, char ** argv) {
+ gint result = 0;
+ IndicatorSoundService* service = NULL;
+
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+
+ /* Initialize libnotify */
+ notify_init ("indicator-sound");
+
+
+ service = indicator_sound_service_new ();
+ result = indicator_sound_service_run (service);
+ g_object_unref(service);
+
+ return result;
+}
+
+
diff --git a/src/main.vala b/src/main.vala
deleted file mode 100644
index 4da9e58..0000000
--- a/src/main.vala
+++ /dev/null
@@ -1,14 +0,0 @@
-
-[CCode (cheader_filename="libintl.h", type="char *")]
-extern unowned string bind_textdomain_codeset (string domainname, string codeset);
-
-static int main (string[] args) {
- bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
- 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 5a12e32..87ca1f0 100644
--- a/src/media-player-list.vala
+++ b/src/media-player-list.vala
@@ -24,24 +24,24 @@
public class MediaPlayerList {
public MediaPlayerList () {
- this._players = new HashTable<string, MediaPlayer> (str_hash, str_equal);
+ this._players = new HashTable<string, MediaPlayerMpris> (str_hash, str_equal);
BusWatcher.watch_namespace (BusType.SESSION, "org.mpris.MediaPlayer2", this.player_appeared, this.player_disappeared);
}
/* only valid while the list is not changed */
public class Iterator {
- HashTableIter<string, MediaPlayer> iter;
+ HashTableIter<string, MediaPlayerMpris> iter;
public Iterator (MediaPlayerList list) {
- this.iter = HashTableIter<string, MediaPlayer> (list._players);
+ this.iter = HashTableIter<string, MediaPlayerMpris> (list._players);
}
public MediaPlayer? next_value () {
- MediaPlayer? player;
+ MediaPlayerMpris? player;
if (this.iter.next (null, out player))
- return player;
+ return player as MediaPlayer;
else
return null;
}
@@ -54,9 +54,9 @@ public class MediaPlayerList {
/**
* Adds the player associated with @desktop_id. Does nothing if such a player already exists.
*/
- MediaPlayer? insert (string desktop_id) {
+ MediaPlayerMpris? insert (string desktop_id) {
var id = desktop_id.has_suffix (".desktop") ? desktop_id : desktop_id + ".desktop";
- MediaPlayer? player = this._players.lookup (id);
+ MediaPlayerMpris? player = this._players.lookup (id);
if (player == null) {
var appinfo = new DesktopAppInfo (id);
@@ -65,7 +65,7 @@ public class MediaPlayerList {
return null;
}
- player = new MediaPlayer (appinfo);
+ player = new MediaPlayerMpris (appinfo);
this._players.insert (player.id, player);
this.player_added (player);
}
@@ -110,7 +110,7 @@ public class MediaPlayerList {
public signal void player_added (MediaPlayer player);
public signal void player_removed (MediaPlayer player);
- HashTable<string, MediaPlayer> _players;
+ HashTable<string, MediaPlayerMpris> _players;
void player_appeared (DBusConnection connection, string name, string owner) {
try {
@@ -126,7 +126,7 @@ public class MediaPlayerList {
}
void player_disappeared (DBusConnection connection, string dbus_name) {
- MediaPlayer? player = this._players.find ( (name, player) => {
+ MediaPlayerMpris? player = this._players.find ( (name, player) => {
return player.dbus_name == dbus_name;
});
diff --git a/src/media-player-mpris.vala b/src/media-player-mpris.vala
new file mode 100644
index 0000000..25ddac4
--- /dev/null
+++ b/src/media-player-mpris.vala
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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/>.
+ *
+ * Authors:
+ * Lars Uebernickel <lars.uebernickel@canonical.com>
+ */
+
+/**
+ * MediaPlayerMpris represents an MRPIS-capable media player.
+ */
+public class MediaPlayerMpris: MediaPlayer {
+
+ public MediaPlayerMpris (DesktopAppInfo appinfo) {
+ this.appinfo = appinfo;
+ }
+
+ /** Desktop id of the player */
+ public override string id {
+ get {
+ return this.appinfo.get_id ();
+ }
+ }
+
+ /** Display name of the player */
+ public override string name {
+ get {
+ return this.appinfo.get_name ();
+ }
+ }
+
+ /** Application icon of the player */
+ public override Icon? icon {
+ get {
+ return this.appinfo.get_icon ();
+ }
+ }
+
+ /**
+ * True if an instance of the player is currently running.
+ *
+ * See also: attach(), detach()
+ */
+ public override bool is_running {
+ get {
+ return this.proxy != null;
+ }
+ }
+
+ /** Name of the player on the bus, if an instance is currently running */
+ public override string dbus_name {
+ get {
+ return this._dbus_name;
+ }
+ }
+
+ public override string state {
+ get; private set; default = "Paused";
+ }
+
+ public override MediaPlayer.Track? current_track {
+ get; set;
+ }
+
+ public override bool can_raise {
+ get {
+ return this.root != null ? this.root.CanRaise : true;
+ }
+ }
+
+ /**
+ * Attach this object to a process of the associated media player. The player must own @dbus_name and
+ * implement the org.mpris.MediaPlayer2.Player interface.
+ *
+ * Only one player can be attached at any given time. Use detach() to detach a player.
+ *
+ * This method does not block. If it is successful, "is-running" will be set to %TRUE.
+ */
+ public void attach (MprisRoot root, string dbus_name) {
+ return_if_fail (this._dbus_name == null && this.proxy == null);
+
+ this.root = root;
+ this.notify_property ("can-raise");
+
+ this._dbus_name = dbus_name;
+ Bus.get_proxy.begin<MprisPlayer> (BusType.SESSION, dbus_name, "/org/mpris/MediaPlayer2",
+ DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, got_proxy);
+ Bus.get_proxy.begin<MprisPlaylists> (BusType.SESSION, dbus_name, "/org/mpris/MediaPlayer2",
+ DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, got_playlists_proxy);
+ }
+
+ /**
+ * Detach this object from a process running the associated media player.
+ *
+ * See also: attach()
+ */
+ public void detach () {
+ this.root = null;
+ this.proxy = null;
+ this._dbus_name = null;
+ this.notify_property ("is-running");
+ this.notify_property ("can-raise");
+ this.state = "Paused";
+ this.current_track = null;
+ }
+
+ /**
+ * Activate the associated media player.
+ *
+ * Note: this will _not_ call attach(), because it doesn't know on which dbus-name the player will appear.
+ * Use attach() to attach this object to a running instance of the player.
+ */
+ public override void activate () {
+ try {
+ if (this.proxy == null) {
+ this.appinfo.launch (null, null);
+ this.state = "Launching";
+ }
+ else if (this.root != null && this.root.CanRaise) {
+ this.root.Raise ();
+ }
+ }
+ catch (Error e) {
+ warning ("unable to activate %s: %s", appinfo.get_name (), e.message);
+ }
+ }
+
+ /**
+ * Toggles playing status.
+ */
+ public override void play_pause () {
+ if (this.proxy != null) {
+ this.proxy.PlayPause.begin ();
+ }
+ else if (this.state != "Launching") {
+ this.play_when_attached = true;
+ this.activate ();
+ }
+ }
+
+ /**
+ * Skips to the next track.
+ */
+ public override void next () {
+ if (this.proxy != null)
+ this.proxy.Next.begin ();
+ }
+
+ /**
+ * Skips to the previous track.
+ */
+ public override void previous () {
+ if (this.proxy != null)
+ this.proxy.Previous.begin ();
+ }
+
+ public override uint get_n_playlists () {
+ return this.playlists != null ? this.playlists.length : 0;
+ }
+
+ public override string get_playlist_id (int index) {
+ return_val_if_fail (index < this.playlists.length, "");
+ return this.playlists[index].path;
+ }
+
+ public override string get_playlist_name (int index) {
+ return_val_if_fail (index < this.playlists.length, "");
+ return this.playlists[index].name;
+ }
+
+ public override void activate_playlist_by_name (string name) {
+ if (this.playlists_proxy != null)
+ this.playlists_proxy.ActivatePlaylist.begin (new ObjectPath (name));
+ }
+
+ DesktopAppInfo appinfo;
+ MprisPlayer? proxy;
+ MprisPlaylists ?playlists_proxy;
+ string _dbus_name;
+ bool play_when_attached = false;
+ MprisRoot root;
+ PlaylistDetails[] playlists = null;
+
+ void got_proxy (Object? obj, AsyncResult res) {
+ try {
+ this.proxy = Bus.get_proxy.end (res);
+
+ /* Connecting to GDBusProxy's "g-properties-changed" signal here, because vala's dbus objects don't
+ * emit notify signals */
+ var gproxy = this.proxy as DBusProxy;
+ gproxy.g_properties_changed.connect (this.proxy_properties_changed);
+
+ this.notify_property ("is-running");
+ this.state = this.proxy.PlaybackStatus;
+ this.update_current_track (gproxy.get_cached_property ("Metadata"));
+
+ if (this.play_when_attached) {
+ /* wait a little before calling PlayPause, some players need some time to
+ set themselves up */
+ Timeout.add (1000, () => { proxy.PlayPause.begin (); return false; } );
+ this.play_when_attached = false;
+ }
+ }
+ catch (Error e) {
+ this._dbus_name = null;
+ warning ("unable to attach to media player: %s", e.message);
+ }
+ }
+
+ void fetch_playlists () {
+ /* The proxy is created even when the interface is not supported. GDBusProxy will
+ return 0 for the PlaylistCount property in that case. */
+ if (this.playlists_proxy != null && this.playlists_proxy.PlaylistCount > 0) {
+ this.playlists_proxy.GetPlaylists.begin (0, 100, "Alphabetical", false, (obj, res) => {
+ try {
+ this.playlists = playlists_proxy.GetPlaylists.end (res);
+ this.playlists_changed ();
+ }
+ catch (Error e) {
+ warning ("could not fetch playlists: %s", e.message);
+ this.playlists = null;
+ }
+ });
+ }
+ else {
+ this.playlists = null;
+ this.playlists_changed ();
+ }
+ }
+
+ void got_playlists_proxy (Object? obj, AsyncResult res) {
+ try {
+ this.playlists_proxy = Bus.get_proxy.end (res);
+
+ var gproxy = this.proxy as DBusProxy;
+ gproxy.g_properties_changed.connect (this.playlists_proxy_properties_changed);
+ }
+ catch (Error e) {
+ warning ("unable to create mpris plalists proxy: %s", e.message);
+ return;
+ }
+
+ Timeout.add (500, () => { this.fetch_playlists (); return false; } );
+ }
+
+ /* some players (e.g. Spotify) don't follow the spec closely and pass single strings in metadata fields
+ * where an array of string is expected */
+ static string sanitize_metadata_value (Variant? v) {
+ if (v == null)
+ return "";
+ else if (v.is_of_type (VariantType.STRING))
+ return v.get_string ();
+ else if (v.is_of_type (VariantType.STRING_ARRAY))
+ return string.joinv (",", v.get_strv ());
+
+ warn_if_reached ();
+ return "";
+ }
+
+ void proxy_properties_changed (DBusProxy proxy, Variant changed_properties, string[] invalidated_properties) {
+ if (changed_properties.lookup ("PlaybackStatus", "s", null)) {
+ this.state = this.proxy.PlaybackStatus;
+ }
+
+ var metadata = changed_properties.lookup_value ("Metadata", new VariantType ("a{sv}"));
+ if (metadata != null)
+ this.update_current_track (metadata);
+ }
+
+ void playlists_proxy_properties_changed (DBusProxy proxy, Variant changed_properties, string[] invalidated_properties) {
+ if (changed_properties.lookup ("PlaylistCount", "u", null))
+ this.fetch_playlists ();
+ }
+
+ void update_current_track (Variant metadata) {
+ if (metadata != null) {
+ this.current_track = new Track (
+ sanitize_metadata_value (metadata.lookup_value ("xesam:artist", null)),
+ sanitize_metadata_value (metadata.lookup_value ("xesam:title", null)),
+ sanitize_metadata_value (metadata.lookup_value ("xesam:album", null)),
+ sanitize_metadata_value (metadata.lookup_value ("mpris:artUrl", null))
+ );
+ }
+ else {
+ this.current_track = null;
+ }
+ }
+}
diff --git a/src/media-player.vala b/src/media-player.vala
index da68ac1..4d4aef3 100644
--- a/src/media-player.vala
+++ b/src/media-player.vala
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 Canonical Ltd.
+ * Copyright © 2014 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,60 +14,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
- * Lars Uebernickel <lars.uebernickel@canonical.com>
+ * Ted Gould <ted@canonical.com>
*/
-/**
- * MediaPlayer represents an MRPIS-capable media player.
- */
-public class MediaPlayer: Object {
-
- public MediaPlayer (DesktopAppInfo appinfo) {
- this.appinfo = appinfo;
- }
-
- /** Desktop id of the player */
- public string id {
- get {
- return this.appinfo.get_id ();
- }
- }
+public abstract class MediaPlayer : Object {
+ public virtual string id { get { not_implemented(); return ""; } }
+ public virtual string name { get { not_implemented(); return ""; } }
+ public virtual string state { get { not_implemented(); return ""; } set { }}
+ public virtual Icon? icon { get { not_implemented(); return null; } }
+ public virtual string dbus_name { get { not_implemented(); return ""; } }
- /** Display name of the player */
- public string name {
- get {
- return this.appinfo.get_name ();
- }
- }
-
- /** Application icon of the player */
- public Icon icon {
- get {
- return this.appinfo.get_icon ();
- }
- }
-
- /**
- * True if an instance of the player is currently running.
- *
- * See also: attach(), detach()
- */
- public bool is_running {
- get {
- return this.proxy != null;
- }
- }
-
- /** Name of the player on the bus, if an instance is currently running */
- public string dbus_name {
- get {
- return this._dbus_name;
- }
- }
-
- public string state {
- get; private set; default = "Paused";
- }
+ public virtual bool is_running { get { not_implemented(); return false; } }
+ public virtual bool can_raise { get { not_implemented(); return false; } }
public class Track : Object {
public string artist { get; construct; }
@@ -80,233 +38,24 @@ public class MediaPlayer: Object {
}
}
- public Track current_track {
- get; set;
- }
-
- public bool can_raise {
- get {
- return this.root != null ? this.root.CanRaise : true;
- }
+ public virtual Track? current_track {
+ get { not_implemented(); return null; }
+ set { not_implemented(); }
}
public signal void playlists_changed ();
- /**
- * Attach this object to a process of the associated media player. The player must own @dbus_name and
- * implement the org.mpris.MediaPlayer2.Player interface.
- *
- * Only one player can be attached at any given time. Use detach() to detach a player.
- *
- * This method does not block. If it is successful, "is-running" will be set to %TRUE.
- */
- public void attach (MprisRoot root, string dbus_name) {
- return_if_fail (this._dbus_name == null && this.proxy == null);
-
- this.root = root;
- this.notify_property ("can-raise");
-
- this._dbus_name = dbus_name;
- Bus.get_proxy.begin<MprisPlayer> (BusType.SESSION, dbus_name, "/org/mpris/MediaPlayer2",
- DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, got_proxy);
- Bus.get_proxy.begin<MprisPlaylists> (BusType.SESSION, dbus_name, "/org/mpris/MediaPlayer2",
- DBusProxyFlags.GET_INVALIDATED_PROPERTIES, null, got_playlists_proxy);
- }
-
- /**
- * Detach this object from a process running the associated media player.
- *
- * See also: attach()
- */
- public void detach () {
- this.root = null;
- this.proxy = null;
- this._dbus_name = null;
- this.notify_property ("is-running");
- this.notify_property ("can-raise");
- this.state = "Paused";
- this.current_track = null;
- }
+ public abstract void activate ();
+ public abstract void play_pause ();
+ public abstract void next ();
+ public abstract void previous ();
- /**
- * Activate the associated media player.
- *
- * Note: this will _not_ call attach(), because it doesn't know on which dbus-name the player will appear.
- * Use attach() to attach this object to a running instance of the player.
- */
- public void activate () {
- try {
- if (this.proxy == null) {
- this.appinfo.launch (null, null);
- this.state = "Launching";
- }
- else if (this.root != null && this.root.CanRaise) {
- this.root.Raise ();
- }
- }
- catch (Error e) {
- warning ("unable to activate %s: %s", appinfo.get_name (), e.message);
- }
- }
-
- /**
- * Toggles playing status.
- */
- public void play_pause () {
- if (this.proxy != null) {
- this.proxy.PlayPause.begin ();
- }
- else if (this.state != "Launching") {
- this.play_when_attached = true;
- this.activate ();
- }
- }
-
- /**
- * Skips to the next track.
- */
- public void next () {
- if (this.proxy != null)
- this.proxy.Next.begin ();
- }
-
- /**
- * Skips to the previous track.
- */
- public void previous () {
- if (this.proxy != null)
- this.proxy.Previous.begin ();
- }
+ public abstract uint get_n_playlists();
+ public abstract string get_playlist_id (int index);
+ public abstract string get_playlist_name (int index);
+ public abstract void activate_playlist_by_name (string playlist);
- public uint get_n_playlists () {
- return this.playlists != null ? this.playlists.length : 0;
- }
-
- public string get_playlist_id (int index) {
- return_val_if_fail (index < this.playlists.length, "");
- return this.playlists[index].path;
- }
-
- public string get_playlist_name (int index) {
- return_val_if_fail (index < this.playlists.length, "");
- return this.playlists[index].name;
- }
-
- public void activate_playlist_by_name (string name) {
- if (this.playlists_proxy != null)
- this.playlists_proxy.ActivatePlaylist.begin (new ObjectPath (name));
- }
-
- DesktopAppInfo appinfo;
- MprisPlayer? proxy;
- MprisPlaylists ?playlists_proxy;
- string _dbus_name;
- bool play_when_attached = false;
- MprisRoot root;
- PlaylistDetails[] playlists = null;
-
- void got_proxy (Object? obj, AsyncResult res) {
- try {
- this.proxy = Bus.get_proxy.end (res);
-
- /* Connecting to GDBusProxy's "g-properties-changed" signal here, because vala's dbus objects don't
- * emit notify signals */
- var gproxy = this.proxy as DBusProxy;
- gproxy.g_properties_changed.connect (this.proxy_properties_changed);
-
- this.notify_property ("is-running");
- this.state = this.proxy.PlaybackStatus;
- this.update_current_track (gproxy.get_cached_property ("Metadata"));
-
- if (this.play_when_attached) {
- /* wait a little before calling PlayPause, some players need some time to
- set themselves up */
- Timeout.add (1000, () => { proxy.PlayPause.begin (); return false; } );
- this.play_when_attached = false;
- }
- }
- catch (Error e) {
- this._dbus_name = null;
- warning ("unable to attach to media player: %s", e.message);
- }
- }
-
- void fetch_playlists () {
- /* The proxy is created even when the interface is not supported. GDBusProxy will
- return 0 for the PlaylistCount property in that case. */
- if (this.playlists_proxy != null && this.playlists_proxy.PlaylistCount > 0) {
- this.playlists_proxy.GetPlaylists.begin (0, 100, "Alphabetical", false, (obj, res) => {
- try {
- this.playlists = playlists_proxy.GetPlaylists.end (res);
- this.playlists_changed ();
- }
- catch (Error e) {
- warning ("could not fetch playlists: %s", e.message);
- this.playlists = null;
- }
- });
- }
- else {
- this.playlists = null;
- this.playlists_changed ();
- }
- }
-
- void got_playlists_proxy (Object? obj, AsyncResult res) {
- try {
- this.playlists_proxy = Bus.get_proxy.end (res);
-
- var gproxy = this.proxy as DBusProxy;
- gproxy.g_properties_changed.connect (this.playlists_proxy_properties_changed);
- }
- catch (Error e) {
- warning ("unable to create mpris plalists proxy: %s", e.message);
- return;
- }
-
- Timeout.add (500, () => { this.fetch_playlists (); return false; } );
- }
-
- /* some players (e.g. Spotify) don't follow the spec closely and pass single strings in metadata fields
- * where an array of string is expected */
- static string sanitize_metadata_value (Variant? v) {
- if (v == null)
- return "";
- else if (v.is_of_type (VariantType.STRING))
- return v.get_string ();
- else if (v.is_of_type (VariantType.STRING_ARRAY))
- return string.joinv (",", v.get_strv ());
-
- warn_if_reached ();
- return "";
- }
-
- void proxy_properties_changed (DBusProxy proxy, Variant changed_properties, string[] invalidated_properties) {
- if (changed_properties.lookup ("PlaybackStatus", "s", null)) {
- this.state = this.proxy.PlaybackStatus;
- }
-
- var metadata = changed_properties.lookup_value ("Metadata", new VariantType ("a{sv}"));
- if (metadata != null)
- this.update_current_track (metadata);
- }
-
- void playlists_proxy_properties_changed (DBusProxy proxy, Variant changed_properties, string[] invalidated_properties) {
- if (changed_properties.lookup ("PlaylistCount", "u", null))
- this.fetch_playlists ();
- }
-
- void update_current_track (Variant metadata) {
- if (metadata != null) {
- this.current_track = new Track (
- sanitize_metadata_value (metadata.lookup_value ("xesam:artist", null)),
- sanitize_metadata_value (metadata.lookup_value ("xesam:title", null)),
- sanitize_metadata_value (metadata.lookup_value ("xesam:album", null)),
- sanitize_metadata_value (metadata.lookup_value ("mpris:artUrl", null))
- );
- }
- else {
- this.current_track = null;
- }
+ private void not_implemented () {
+ warning("Property not implemented");
}
}
diff --git a/src/service.vala b/src/service.vala
index d4a5bc6..f6c5f01 100644
--- a/src/service.vala
+++ b/src/service.vala
@@ -46,6 +46,10 @@ public class IndicatorSound.Service: Object {
this.volume_control.bind_property ("active-mic", menu, "show-mic-volume", BindingFlags.SYNC_CREATE);
});
+ /* Setup handling for the greeter-export setting */
+ this.settings.changed["greeter-export"].connect( () => this.build_accountsservice() );
+ build_accountsservice();
+
this.sync_preferred_players ();
this.settings.changed["interested-media-players"].connect ( () => {
this.sync_preferred_players ();
@@ -62,6 +66,41 @@ public class IndicatorSound.Service: Object {
sharedsettings.bind ("allow-amplified-volume", this, "allow-amplified-volume", SettingsBindFlags.GET);
}
+ ~Service() {
+ if (this.sound_was_blocked_timeout_id > 0) {
+ Source.remove (this.sound_was_blocked_timeout_id);
+ this.sound_was_blocked_timeout_id = 0;
+ }
+ }
+
+ void build_accountsservice () {
+ clear_acts_player();
+ this.accounts_service = null;
+
+ /* If we're not exporting, don't build anything */
+ if (!this.settings.get_boolean("greeter-export")) {
+ debug("Accounts service export disabled due to user setting");
+ return;
+ }
+
+ /* If we're on the greeter, don't export */
+ if (GLib.Environment.get_user_name() == "lightdm") {
+ debug("Accounts service export disabled due to being used on the greeter");
+ return;
+ }
+
+ this.accounts_service = new AccountsServiceUser();
+
+ this.eventually_update_player_actions();
+ }
+
+ void clear_acts_player () {
+ /* NOTE: This is a bit of a hack to ensure that accounts service doesn't
+ continue to export the player by keeping a ref in the timer */
+ if (this.accounts_service != null)
+ this.accounts_service.player = null;
+ }
+
public int run () {
if (this.loop != null) {
warning ("service is already running");
@@ -72,8 +111,17 @@ public class IndicatorSound.Service: Object {
this.bus_acquired, null, this.name_lost);
this.loop = new MainLoop (null, false);
+
+ GLib.Unix.signal_add(GLib.ProcessSignal.TERM, () => {
+ debug("SIGTERM recieved, stopping our mainloop");
+ this.loop.quit();
+ return false;
+ });
+
this.loop.run ();
+ clear_acts_player();
+
return 0;
}
@@ -117,6 +165,7 @@ public class IndicatorSound.Service: Object {
uint sound_was_blocked_timeout_id;
Notify.Notification notification;
bool syncing_preferred_players = false;
+ AccountsServiceUser? accounts_service = null;
/* Maximum volume as a scaling factor between the volume action's state and the value in
* this.volume_control. See create_volume_action().
@@ -343,14 +392,25 @@ public class IndicatorSound.Service: Object {
}
bool update_player_actions () {
+ bool clear_accounts_player = true;
+
foreach (var player in this.players) {
SimpleAction? action = this.actions.lookup_action (player.id) as SimpleAction;
if (action != null) {
action.set_state (this.action_state_for_player (player));
action.set_enabled (player.can_raise);
}
+
+ /* If we're playing then put that data in accounts service */
+ if (player.is_running && accounts_service != null) {
+ accounts_service.player = player;
+ clear_accounts_player = false;
+ }
}
+ if (clear_accounts_player)
+ clear_acts_player();
+
this.player_action_update_id = 0;
return false;
}
diff --git a/src/volume-control.vala b/src/volume-control.vala
index 4347ce5..889c2d6 100644
--- a/src/volume-control.vala
+++ b/src/volume-control.vala
@@ -73,6 +73,7 @@ public class VolumeControl : Object
{
if (_reconnect_timer != 0) {
Source.remove (_reconnect_timer);
+ _reconnect_timer = 0;
}
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 8e79fd0..1556fc7 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -10,6 +10,77 @@ add_library (gtest STATIC
${GTEST_SOURCE_DIR}/gtest_main.cc)
target_link_libraries(gtest ${GTEST_LIBS})
+###########################
+# Vala Mocks
+###########################
+
+set(VALA_MOCKS_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/vala-mocks.h")
+set(VALA_MOCKS_SYMBOLS_PATH "${CMAKE_CURRENT_BINARY_DIR}/vala-mocks.def")
+
+vala_init(vala-mocks
+ DEPENDS
+ indicator-sound-service-lib
+ PACKAGES
+ config
+ gio-2.0
+ gio-unix-2.0
+ libxml-2.0
+ libpulse
+ libpulse-mainloop-glib
+ libnotify
+ accounts-service
+ indicator-sound-service
+ OPTIONS
+ --ccode
+ --thread
+ --vapidir=${CMAKE_BINARY_DIR}/src/
+ --vapidir=${CMAKE_SOURCE_DIR}/vapi/
+ --vapidir=.
+)
+
+vala_add(vala-mocks
+ media-player-mock.vala
+)
+
+vala_finish(vala-mocks
+ SOURCES
+ vala_mocks_VALA_SOURCES
+ OUTPUTS
+ vala_mocks_VALA_C
+ GENERATE_HEADER
+ ${VALA_MOCKS_HEADER_PATH}
+ GENERATE_SYMBOLS
+ ${VALA_MOCKS_SYMBOLS_PATH}
+)
+
+set_source_files_properties(
+ ${vala_mocks_VALA_SOURCES}
+ PROPERTIES
+ HEADER_FILE_ONLY TRUE
+)
+
+set(
+ VALA_MOCKS_SOURCES
+ ${vala_mocks_VALA_SOURCES}
+ ${vala_mocks_VALA_C}
+ ${VALA_MOCKS_SYMBOLS_PATH}
+)
+
+add_definitions(
+ -Wno-unused-but-set-variable
+)
+
+add_library(
+ vala-mocks-lib STATIC
+ ${VALA_MOCKS_SOURCES}
+)
+
+target_link_libraries(
+ vala-mocks-lib
+ indicator-sound-service-lib
+)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
###########################
# Name Watch Test
@@ -20,3 +91,26 @@ add_executable (name-watch-test name-watch-test.cc ${CMAKE_SOURCE_DIR}/src/bus-w
target_link_libraries (name-watch-test gtest ${SOUNDSERVICE_LIBRARIES})
add_test(name-watch-test name-watch-test)
+###########################
+# Accounts Service User
+###########################
+
+include_directories(${CMAKE_SOURCE_DIR}/src)
+add_executable (accounts-service-user-test accounts-service-user.cc)
+target_link_libraries (
+ accounts-service-user-test
+ indicator-sound-service-lib
+ vala-mocks-lib
+ gtest
+ ${SOUNDSERVICE_LIBRARIES}
+ ${TEST_LIBRARIES}
+)
+
+# Split tests to work around libaccountservice sucking
+add_test(accounts-service-user-test-basic
+ accounts-service-user-test --gtest_filter=AccountsServiceUserTest.BasicObject
+)
+
+add_test(accounts-service-user-test-player
+ accounts-service-user-test --gtest_filter=AccountsServiceUserTest.SetMediaPlayer
+)
diff --git a/tests/accounts-service-mock.h b/tests/accounts-service-mock.h
new file mode 100644
index 0000000..225d7b5
--- /dev/null
+++ b/tests/accounts-service-mock.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2014 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ */
+
+#include <libdbustest/dbus-test.h>
+
+class AccountsServiceMock
+{
+ DbusTestDbusMock * mock = nullptr;
+
+ public:
+ AccountsServiceMock () {
+ mock = dbus_test_dbus_mock_new("org.freedesktop.Accounts");
+
+ DbusTestDbusMockObject * baseobj = dbus_test_dbus_mock_get_object(mock, "/org/freedesktop/Accounts", "org.freedesktop.Accounts", NULL);
+
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "CacheUser", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH,
+ "ret = dbus.ObjectPath('/user')\n", NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "FindUserById", G_VARIANT_TYPE_INT64, G_VARIANT_TYPE_OBJECT_PATH,
+ "ret = dbus.ObjectPath('/user')\n", NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "FindUserByName", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH,
+ "ret = dbus.ObjectPath('/user')\n", NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "ListCachedUsers", NULL, G_VARIANT_TYPE_OBJECT_PATH_ARRAY,
+ "ret = [ dbus.ObjectPath('/user') ]\n", NULL);
+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
+ "UncacheUser", G_VARIANT_TYPE_STRING, NULL,
+ "", NULL);
+
+ DbusTestDbusMockObject * userobj = dbus_test_dbus_mock_get_object(mock, "/user", "org.freedesktop.Accounts.User", NULL);
+ dbus_test_dbus_mock_object_add_property(mock, userobj,
+ "UserName", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(g_get_user_name()), NULL);
+
+ DbusTestDbusMockObject * soundobj = dbus_test_dbus_mock_get_object(mock, "/user", "com.canonical.indicator.sound.AccountsService", NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Timestamp", G_VARIANT_TYPE_UINT64,
+ g_variant_new_uint64(0), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "PlayerName", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "PlayerIcon", G_VARIANT_TYPE_VARIANT,
+ g_variant_new_variant(g_variant_new_string("")), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Running", G_VARIANT_TYPE_BOOLEAN,
+ g_variant_new_boolean(FALSE), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "State", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Title", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Artist", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "Album", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
+ "ArtUrl", G_VARIANT_TYPE_STRING,
+ g_variant_new_string(""), NULL);
+ }
+
+ ~AccountsServiceMock () {
+ g_clear_object(&mock);
+ }
+
+ operator DbusTestTask* () {
+ return DBUS_TEST_TASK(mock);
+ }
+};
diff --git a/tests/accounts-service-user.cc b/tests/accounts-service-user.cc
new file mode 100644
index 0000000..b39b546
--- /dev/null
+++ b/tests/accounts-service-user.cc
@@ -0,0 +1,201 @@
+/*
+ * Copyright © 2014 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ */
+
+#include <gtest/gtest.h>
+#include <gio/gio.h>
+#include <libdbustest/dbus-test.h>
+#include <act/act.h>
+
+#include "accounts-service-mock.h"
+
+extern "C" {
+#include "indicator-sound-service.h"
+#include "vala-mocks.h"
+}
+
+class AccountsServiceUserTest : public ::testing::Test
+{
+
+ protected:
+ DbusTestService * service = NULL;
+ DbusTestDbusMock * mock = NULL;
+
+ GDBusConnection * session = NULL;
+ GDBusConnection * system = NULL;
+ GDBusProxy * proxy = NULL;
+
+ virtual void SetUp() {
+ service = dbus_test_service_new(NULL);
+
+ AccountsServiceMock service_mock;
+
+ dbus_test_service_add_task(service, (DbusTestTask*)service_mock);
+ dbus_test_service_start_tasks(service);
+
+ g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE);
+
+ session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
+ ASSERT_NE(nullptr, session);
+ g_dbus_connection_set_exit_on_close(session, FALSE);
+ g_object_add_weak_pointer(G_OBJECT(session), (gpointer *)&session);
+
+ system = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
+ ASSERT_NE(nullptr, system);
+ g_dbus_connection_set_exit_on_close(system, FALSE);
+ g_object_add_weak_pointer(G_OBJECT(system), (gpointer *)&system);
+
+ proxy = g_dbus_proxy_new_sync(session,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.Accounts",
+ "/user",
+ "org.freedesktop.DBus.Properties",
+ NULL, NULL);
+ ASSERT_NE(nullptr, proxy);
+ }
+
+ virtual void TearDown() {
+ g_clear_object(&proxy);
+ g_clear_object(&mock);
+ g_clear_object(&service);
+
+ g_object_unref(session);
+ g_object_unref(system);
+
+ #if 0
+ /* Accounts Service keeps a bunch of references around so we
+ have to split the tests and can't check this :-( */
+ unsigned int cleartry = 0;
+ while ((session != NULL || system != NULL) && cleartry < 100) {
+ loop(100);
+ cleartry++;
+ }
+
+ ASSERT_EQ(nullptr, session);
+ ASSERT_EQ(nullptr, system);
+ #endif
+ }
+
+ static gboolean timeout_cb (gpointer user_data) {
+ GMainLoop * loop = static_cast<GMainLoop *>(user_data);
+ g_main_loop_quit(loop);
+ return G_SOURCE_REMOVE;
+ }
+
+ void loop (unsigned int ms) {
+ GMainLoop * loop = g_main_loop_new(NULL, FALSE);
+ g_timeout_add(ms, timeout_cb, loop);
+ g_main_loop_run(loop);
+ g_main_loop_unref(loop);
+ }
+
+ static int unref_idle (gpointer user_data) {
+ g_variant_unref(static_cast<GVariant *>(user_data));
+ return G_SOURCE_REMOVE;
+ }
+
+ const gchar * get_property_string (const gchar * name) {
+ GVariant * propval = g_dbus_proxy_call_sync(proxy,
+ "Get",
+ g_variant_new("(ss)", "com.canonical.indicator.sound.AccountsService", name),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, NULL
+ );
+
+ if (propval == nullptr) {
+ return nullptr;
+ }
+
+ /* NOTE: This is a bit of a hack, basically if main gets
+ called the returned string becomes invalid. But it
+ makes the test much easier to read :-/ */
+ g_idle_add(unref_idle, propval);
+
+ const gchar * ret = NULL;
+ GVariant * child = g_variant_get_child_value(propval, 0);
+ GVariant * vstr = g_variant_get_variant(child);
+ ret = g_variant_get_string(vstr, NULL);
+ g_variant_unref(vstr);
+ g_variant_unref(child);
+
+ return ret;
+ }
+};
+
+TEST_F(AccountsServiceUserTest, BasicObject) {
+ AccountsServiceUser * srv = accounts_service_user_new();
+ loop(50);
+ g_object_unref(srv);
+}
+
+TEST_F(AccountsServiceUserTest, SetMediaPlayer) {
+ MediaPlayerTrack * track = media_player_track_new("Artist", "Title", "Album", "http://art.url");
+
+ MediaPlayerMock * media = MEDIA_PLAYER_MOCK(
+ g_object_new(TYPE_MEDIA_PLAYER_MOCK,
+ "mock-id", "player-id",
+ "mock-name", "Test Player",
+ "mock-state", "Playing",
+ "mock-is-running", TRUE,
+ "mock-can-raise", FALSE,
+ "mock-current-track", track,
+ NULL)
+ );
+ g_clear_object(&track);
+
+ AccountsServiceUser * srv = accounts_service_user_new();
+
+ accounts_service_user_set_player(srv, MEDIA_PLAYER(media));
+
+ loop(500);
+
+ /* Verify the values are on the other side of the bus */
+ EXPECT_STREQ("Test Player", get_property_string("PlayerName"));
+ EXPECT_STREQ("Playing", get_property_string("State"));
+ EXPECT_STREQ("Title", get_property_string("Title"));
+ EXPECT_STREQ("Artist", get_property_string("Artist"));
+ EXPECT_STREQ("Album", get_property_string("Album"));
+ EXPECT_STREQ("http://art.url", get_property_string("ArtUrl"));
+
+ /* Check changing the track info */
+ track = media_player_track_new("Artist-ish", "Title-like", "Psuedo Album", "http://fake.art.url");
+ media_player_mock_set_mock_current_track(media, track);
+ g_clear_object(&track);
+ accounts_service_user_set_player(srv, MEDIA_PLAYER(media));
+
+ loop(500);
+
+ EXPECT_STREQ("Test Player", get_property_string("PlayerName"));
+ EXPECT_STREQ("Playing", get_property_string("State"));
+ EXPECT_STREQ("Title-like", get_property_string("Title"));
+ EXPECT_STREQ("Artist-ish", get_property_string("Artist"));
+ EXPECT_STREQ("Psuedo Album", get_property_string("Album"));
+ EXPECT_STREQ("http://fake.art.url", get_property_string("ArtUrl"));
+
+ /* Check to ensure the state can be updated */
+ media_player_set_state(MEDIA_PLAYER(media), "Paused");
+ accounts_service_user_set_player(srv, MEDIA_PLAYER(media));
+
+ loop(500);
+
+ EXPECT_STREQ("Paused", get_property_string("State"));
+
+ g_object_unref(media);
+ g_object_unref(srv);
+}
diff --git a/tests/media-player-mock.vala b/tests/media-player-mock.vala
new file mode 100644
index 0000000..14028f5
--- /dev/null
+++ b/tests/media-player-mock.vala
@@ -0,0 +1,76 @@
+/*
+ * Copyright © 2014 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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/>.
+ *
+ * Authors:
+ * Ted Gould <ted@canonical.com>
+ */
+
+public class MediaPlayerMock: MediaPlayer {
+
+ /* Superclass variables */
+ public override string id { get { return mock_id; } }
+ public override string name { get { return mock_name; } }
+ public override string state { get { return mock_state; } set { this.mock_state = value; }}
+ public override Icon? icon { get { return mock_icon; } }
+ public override string dbus_name { get { return mock_dbus_name; } }
+
+ public override bool is_running { get { return mock_is_running; } }
+ public override bool can_raise { get { return mock_can_raise; } }
+
+ public override MediaPlayer.Track? current_track { get { return mock_current_track; } set { this.mock_current_track = value; } }
+
+ /* Mock values */
+ public string mock_id { get; set; }
+ public string mock_name { get; set; }
+ public string mock_state { get; set; }
+ public Icon? mock_icon { get; set; }
+ public string mock_dbus_name { get; set; }
+
+ public bool mock_is_running { get; set; }
+ public bool mock_can_raise { get; set; }
+
+ public MediaPlayer.Track? mock_current_track { get; set; }
+
+ /* Virtual functions */
+ public override void activate () {
+ debug("Mock activate");
+ }
+ public override void play_pause () {
+ debug("Mock play_pause");
+ }
+ public override void next () {
+ debug("Mock next");
+ }
+ public override void previous () {
+ debug("Mock previous");
+ }
+
+ public override uint get_n_playlists() {
+ debug("Mock get_n_playlists");
+ return 0;
+ }
+ public override string get_playlist_id (int index) {
+ debug("Mock get_playlist_id");
+ return "";
+ }
+ public override string get_playlist_name (int index) {
+ debug("Mock get_playlist_name");
+ return "";
+ }
+ public override void activate_playlist_by_name (string playlist) {
+ debug("Mock activate_playlist_by_name");
+ }
+
+}