summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <daniel.silverstone@codethink.co.uk>2011-10-29 22:33:44 +0100
committerDaniel Silverstone <daniel.silverstone@codethink.co.uk>2011-10-29 22:33:44 +0100
commite62815307860d7e90ba63e281c7ed8776ef43f00 (patch)
tree5035430863455de628ada791fd5bbcd3e545959d
downloadspotify-notify-e62815307860d7e90ba63e281c7ed8776ef43f00.tar.bz2
Initial rev
-rw-r--r--Makefile11
-rw-r--r--spotify-notify.vala285
2 files changed, 296 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d28218c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+all: spotify-notify
+
+clean:
+ $(RM) spotify-notify
+
+distclean: clean
+ $(RM) *~
+
+spotify-notify: spotify-notify.vala
+ valac --pkg gio-2.0 --pkg posix --pkg libsoup-2.4 $<
+
diff --git a/spotify-notify.vala b/spotify-notify.vala
new file mode 100644
index 0000000..1c97a5c
--- /dev/null
+++ b/spotify-notify.vala
@@ -0,0 +1,285 @@
+[DBus (name = "org.mpris.MediaPlayer2.Player")]
+interface Player : Object {
+ public abstract void play_pause () throws IOError;
+ public abstract void next () throws IOError;
+ public abstract void previous () throws IOError;
+ public abstract void stop () throws IOError;
+
+}
+
+[DBus (name = "org.freedesktop.DBus.Properties")]
+interface PlayerProperties : Object {
+ public signal void properties_changed (string iface, HashTable<string, Variant> props, string[] invalidateds);
+ public abstract void get (string iface, string prop, out Variant value) throws IOError;
+}
+
+[DBus (name = "org.gnome.SettingsDaemon.MediaKeys")]
+interface MediaKeys : Object {
+ public abstract void grab_media_player_keys(string appname, uint32 n) throws IOError;
+ public signal void media_player_key_pressed (string appname, string key);
+}
+
+[DBus (name = "org.freedesktop.DBus")]
+interface BusInterface : Object {
+ public signal void name_owner_changed (string name, string old_owner, string new_owner);
+ public abstract void name_has_owner (string name, out bool has_owner) throws IOError;
+}
+
+[DBus (name = "org.freedesktop.Notifications")]
+interface Notify : Object {
+ public abstract uint32 notify (string app_name, uint32 replaces_id, string icon, string summary, string body,
+ string[] actions, HashTable<string, Variant> hints, int32 timeout) throws IOError;
+ public abstract void close_notification (uint32 id) throws IOError;
+ public signal void notification_closed (uint32 id, uint32 reason);
+
+}
+
+Player player = null;
+PlayerProperties playerprops = null;
+MainLoop loop;
+Notify notify;
+uint32 notification_id = 0;
+Soup.SessionAsync soupsession;
+
+
+const string imgcache = "/home/dsilvers/.cache/spotify-albums";
+
+void cancel_notification() {
+ try {
+ if (notification_id > 0) {
+ stdout.printf("Cancelling notification %u\n", notification_id);
+ notify.close_notification(notification_id);
+ stdout.printf("Notification cancelled\n");
+ notification_id = 0;
+ }
+ } catch (IOError e) {
+ stderr.printf("Error closing notification: %s\n", e.message);
+ }
+}
+
+class cbdobj {
+ public string uri;
+ public string targetfile;
+ public Variant metadata;
+ public bool unsolicited;
+
+ public void fetch_image_cb(Soup.Session sess, Soup.Message mess)
+ {
+ stdout.printf("Response for %s => %s\n", uri, targetfile);
+ stdout.printf("Status code %u, Content length: %zd\n", mess.status_code, (ssize_t)mess.response_body.length);
+ if (mess.response_body.length > 0) {
+ stdout.printf("Attempting to write to %s\n", targetfile);
+ FileStream? f = FileStream.open(targetfile, "wb");
+ if (f != null) {
+ f.write(mess.response_body.data);
+ f = null; /* fclose */
+ stdout.printf("Re-displaying with image for %s\n", uri);
+ metadata_changed(metadata, unsolicited);
+ }
+ }
+ /* We're done, so delete us */
+ delete *&this;
+ }
+}
+
+void fetch_image(string targetfile, string uri, Variant metadata, bool unsolicited) {
+ var msg = new Soup.Message ("GET", uri);
+ stdout.printf("Prepping async fetch of %s\n", uri);
+ cbdobj *cbd = new cbdobj ();
+ cbd->uri = uri;
+ cbd->targetfile = targetfile;
+ cbd->metadata = metadata;
+ cbd->unsolicited = unsolicited;
+ soupsession.queue_message (msg, cbd->fetch_image_cb);
+}
+
+void on_notification_closed (uint32 id, uint32 reason) {
+ stdout.printf("Notification %u closed\n", id);
+ if (notification_id == id) {
+ stdout.printf("That was us\n");
+ notification_id = 0;
+ }
+}
+
+void metadata_changed(Variant metadata, bool unsolicited) {
+ Variant? v_artist_a = metadata.lookup_value("xesam:artist", null);
+ Variant? v_artist = v_artist_a == null ? null : v_artist_a.get_child_value(0);
+ Variant? v_album = metadata.lookup_value("xesam:album", null);
+ Variant? v_title = metadata.lookup_value("xesam:title", null);
+ Variant? v_arturl = metadata.lookup_value("mpris:artUrl", null);
+ string? arturl = v_arturl == null ? null : v_arturl.get_string();
+
+ if (v_artist == null ||
+ v_album == null ||
+ v_title == null) {
+ stderr.printf("Incomplete metadata!\n");
+ return;
+ }
+
+ string imgpath = "";
+ string? art_id;
+
+ bool need_load_artwork = false;
+ if (v_arturl != null) {
+ /* Do we have the image cached? */
+ string s_art_id = arturl.substring(arturl.last_index_of_char('/'));
+ art_id = s_art_id.next_char();
+ imgpath = @"$imgcache/$art_id";
+ Posix.Stat sbuf;
+ if (Posix.stat(imgpath, out sbuf) != 0) {
+ stderr.printf("Artwork %s not found\n", arturl);
+ need_load_artwork = true;
+ } else {
+ stderr.printf("Artwork %s found\n", imgpath);
+ }
+ }
+
+ string artist = v_artist.get_string();
+ string album = v_album.get_string();
+ string title = v_title.get_string();
+
+ string msg = @"$title\n$album";
+
+ string[] actions = null;
+ HashTable<string, Variant> hints = new HashTable<string, Variant> (null, null);
+
+ try {
+ stdout.printf("Attempting to notify\n");
+ var nid = notify.notify("Spotify Interface",
+ notification_id,
+ imgpath,
+ artist, msg,
+ actions,
+ hints,
+ unsolicited ? 5000 : 1000);
+ notification_id = nid;
+ stdout.printf("Notification %u sent\n", notification_id);
+ } catch (IOError e) {
+ stderr.printf("Error while notifying: %s\n", e.message);
+ }
+ if (need_load_artwork) {
+ stdout.printf("Queueing %s => %s\n", arturl, imgpath);
+ fetch_image(imgpath, arturl, metadata, unsolicited);
+ }
+}
+
+void on_properties_changed (string iface, HashTable<string, Variant> props, string[] invalidateds) {
+ if (iface == "org.mpris.MediaPlayer2.Player") {
+ Variant? mode = props.lookup("PlaybackStatus");
+ if (mode != null) {
+ if (mode.get_string() == "Paused") {
+ /* Something here? */
+ cancel_notification();
+ } else {
+ /* Switched to playing */
+ try {
+ Variant v;
+ playerprops.get ("org.mpris.MediaPlayer2.Player", "Metadata", out v);
+ metadata_changed(v, false);
+ } catch (IOError e) {
+ stderr.printf("Playback metadata retrieval failed: %s\n", e.message);
+ }
+ }
+ }
+ Variant? metadata = props.lookup("Metadata");
+ if (metadata != null) {
+ metadata_changed(metadata, true);
+ }
+ }
+}
+
+void reconnect_spotify () {
+ stdout.printf("Attempting to (re)connect to spotify\n");
+ try {
+ player = Bus.get_proxy_sync(BusType.SESSION, "com.spotify.qt", "/org/mpris/MediaPlayer2");
+ playerprops = Bus.get_proxy_sync(BusType.SESSION, "com.spotify.qt", "/org/mpris/MediaPlayer2");
+ playerprops.properties_changed.connect (on_properties_changed);
+ stdout.printf("Success!\n");
+ } catch (IOError e) {
+ stderr.printf("Error (re)connecting: %s\n", e.message);
+ }
+}
+
+void on_name_owner_changed (string name, string old_owner, string new_owner) {
+ // stdout.printf("name owner changed. name=%s old_owner=%s new_owner=%s\n", name, old_owner, new_owner);
+ if (name == "com.spotify.qt") {
+ if (new_owner == "") {
+ /* Spotify went away */
+ player = null;
+ playerprops = null;
+ stdout.printf("Spotify went away\n");
+ } else {
+ /* Spotify has arrived */
+ reconnect_spotify();
+ }
+ }
+}
+
+void on_media_player_key_pressed (string appname, string key) {
+ stdout.printf("App %s key pressed: %s\n", appname, key);
+
+ if (player == null) {
+ stdout.printf("Key ignored, no spotify running\n");
+ }
+
+ try {
+ switch (key) {
+ case "Play":
+ player.play_pause();
+ break;
+ case "PlayPause":
+ player.play_pause();
+ break;
+ case "Stop":
+ player.stop();
+ break;
+ case "Previous":
+ player.previous();
+ break;
+ case "Next":
+ player.next();
+ break;
+ default:
+ stderr.printf("Unknown media key!\n");
+ break;
+ };
+ } catch (IOError e) {
+ stderr.printf("Error: %s\n", e.message);
+ }
+}
+
+int main() {
+ MediaKeys mkeys;
+ BusInterface bi;
+ try {
+ soupsession = new Soup.SessionAsync ();
+ stdout.printf("Getting media keys\n");
+ mkeys = Bus.get_proxy_sync (BusType.SESSION, "org.gnome.SettingsDaemon", "/org/gnome/SettingsDaemon/MediaKeys");
+ mkeys.media_player_key_pressed.connect (on_media_player_key_pressed);
+ mkeys.grab_media_player_keys("Spotify", 0);
+ stdout.printf("Getting notifications\n");
+ notify = Bus.get_proxy_sync (BusType.SESSION, "org.freedesktop.Notifications", "/org/freedesktop/Notifications");
+ notify.notification_closed.connect (on_notification_closed);
+ stdout.printf("Getting owner handles\n");
+ bi = Bus.get_proxy_sync (BusType.SESSION, "org.freedesktop.DBus", "/org/freedesktop/DBus");
+ bi.name_owner_changed.connect (on_name_owner_changed);
+
+ bool t;
+ bi.name_has_owner("com.spotify.qt", out t);
+
+ if (t)
+ reconnect_spotify();
+ else
+ stdout.printf("Spotify not running, will wait for notify\n");
+
+ } catch (IOError e) {
+ stderr.printf ("%s\n", e.message);
+ return 1;
+ }
+
+ loop = new MainLoop ();
+ loop.run ();
+
+ return 0;
+}
+ \ No newline at end of file