aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt61
-rw-r--r--src/accounts-service-sound-settings.vala33
-rw-r--r--src/accounts-service-user.vala115
-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.vala33
9 files changed, 596 insertions, 308 deletions
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..f021764
--- /dev/null
+++ b/src/accounts-service-user.vala
@@ -0,0 +1,115 @@
+/*
+ * 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) {
+ /* Clear it */
+ this.proxy.player_name = "";
+ this.proxy.timestamp = 0;
+ } 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(() => {
+ debug("User loaded");
+
+ this.proxy = null;
+
+ Bus.get_proxy.begin<AccountsServiceSoundSettings> (
+ BusType.SYSTEM,
+ "org.freedesktop.Accounts",
+ user.get_object_path(),
+ DBusProxyFlags.GET_INVALIDATED_PROPERTIES,
+ null,
+ new_proxy);
+ });
+ }
+
+ ~AccountsServiceUser () {
+ this.player = null;
+ }
+
+ 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..d94aa18 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,22 @@ public class IndicatorSound.Service: Object {
sharedsettings.bind ("allow-amplified-volume", this, "allow-amplified-volume", SettingsBindFlags.GET);
}
+ void build_accountsservice () {
+ this.accounts_service = null;
+
+ /* If we're not exporting, don't build anything */
+ if (!this.settings.get_boolean("greeter-export")) {
+ return;
+ }
+
+ /* If we're on the greeter, don't export */
+ if (GLib.Environment.get_user_name() == "lightdm") {
+ return;
+ }
+
+ this.accounts_service = new AccountsServiceUser();
+ }
+
public int run () {
if (this.loop != null) {
warning ("service is already running");
@@ -117,6 +137,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,12 +364,24 @@ 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 && accounts_service != null) {
+ accounts_service.player = null;
}
this.player_action_update_id = 0;