Muffinresearch Labs by Stuart Colville

Spotify ScreenSaver Toggle with D-bus | 10 Comments

Posted in Linux/Unix, Music on 3rd November 2010, 12:10 am by

UPDATE! – see This post for the latest version of the spotify screensaver toggle

The latest version of the spotify client for linux (0.4.8) has added partial mpris support.

This is great as it means controlling spotify via D-bus is far easier than the previous hacks using wmctrl and xvkbd.

I’ve updated the screensaver toggle script to use spotify’s D-bus interface to play/pause spotify. This also means that I’ve been able to de-couple the script from having to run the screensaver which a big improvement on the old version.

To run it just add the script to your start-up items.

Edit Startup Program Dialogue

Here’s the code, which is a lot more concise compared to the previous version.

Updated to use Mpris2 as of revision 9 for v0.4.8.306…

#!/usr/bin/env python

""" 
Title: Spotify Screensaver Toggle
Author: Stuart Colville, http://muffinresearch.co.uk/
License: BSD

Requires Spotify Linux Preview.

Usage:

 * Save the script
 * Ensure it is executable: chmod +x /path/to/script
 * Add the script to your start-up items. 

"""

import dbus
import gobject
from dbus.mainloop.glib import DBusGMainLoop

class SpotifyScreenSaverPause(object):
    """This pauses and plays spotify when the screensaver is activated"""

    def __init__(self):
        """init class."""
        dbus_loop = DBusGMainLoop(set_as_default=True)
        self.bus = dbus.SessionBus(mainloop = dbus_loop)
        self.loop = gobject.MainLoop()
        self.start_listen()
        self.was_playing = 0
        self.loop.run()

    def start_listen(self):
        """Listen to screensaver ActiveChanged events from dbus."""
        screensaver = self.bus.get_object('org.gnome.ScreenSaver', 
                                                  '/org/gnome/ScreenSaver')
        screensaver.connect_to_signal("ActiveChanged", self.play_pause)

    def play_pause(self, *args, **kwargs):
        """Toggle play/pause."""
        try:
            spotify = self.bus.get_object("org.mpris.MediaPlayer2.spotify", "/")
            spotify_iface = dbus.Interface(spotify, dbus_interface="org.freedesktop.MediaPlayer2")
            if self.was_playing:
                spotify_iface.Play()
                self.was_playing = 0
            else:
                if spotify_iface.GetMetadata():
                    self.was_playing = 1
                spotify_iface.Pause()
        except dbus.exceptions.DBusException, e:
            # Ignore exception caused by spotify not being started.
            if e.get_dbus_name() != 'org.freedesktop.DBus.Error.ServiceUnknown':
                raise

if __name__ == "__main__":
    ss = SpotifyScreenSaverPause()

See the latest version of the code here Spotify Screensaver Toggle

If you want to investigate what’s possible with D-bus and spotify, D-feet is an excellent tool for investigating D-bus:

D-feet D-bus DebuggerClick for a larger version.

Further Examples

Here’s an example to get the metadata from a track currently playing. (Note: if Spotify isn’t playing this will be an empty dictionary.)

>>> import dbus
>>> bus = dbus.SessionBus()
>>> spotify = bus.get_object("org.mpris.spotify", "/")
>>> spotify_iface = dbus.Interface(spotify, dbus_interface="org.freedesktop.MediaPlayer")
>>> spotify_iface.GetMetadata()
dbus.Dictionary({dbus.String(u'album'): dbus.String(u'Getz/Gilberto #2', variant_level=1), dbus.String(u'title'): dbus.String(u"Here's That Rainy Day", variant_level=1), dbus.String(u'artist'): dbus.String(u'Stan Getz', variant_level=1), dbus.String(u'year'): dbus.Int32(1964, variant_level=1), dbus.String(u'location'): dbus.String(u'spotify:track:1RIfT0Y0TwP2M38pLLfDeJ', variant_level=1), dbus.String(u'time'): dbus.UInt32(242L, variant_level=1), dbus.String(u'tracknumber'): dbus.Int32(4, variant_level=1)}, signature=dbus.Signature('sv'))
>>> print spotify_iface.GetMetadata().get("title")
Here's That Rainy Day
>>> print spotify_iface.GetMetadata().get("location")
spotify:track:1BTO5gV9xSAB7EOBKSlcFY
>>>

With the latest Spotify update to use MPRIS2 (0.4.8.306.xxx) – the TrackChanged signal has gone and there’s no PropertiesChanged signal as per MPRIS2 spec http://www.mpris.org/2.0/spec/ I’ll update the notification example script below as soon as that is rectified. It was there all along. I’ve updated the code to reflect the use of PropertiesChanged

To take that example further here’s a demo that listens to the TrackChangedPropertiesChanged event. It uses the notification id so we can replace the notification details as you move through the tracks. This avoids waiting for the timeout before displaying the current track information.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Title: Spotify Notification Demo
Author: Stuart Colville, http://muffinresearch.co.uk
License: BSD

"""

import dbus
import gobject
from dbus.mainloop.glib import DBusGMainLoop
import pynotify

class SpotifyNotifier(object):

    def __init__(self):
        """initialise."""
        bus_loop = DBusGMainLoop(set_as_default=True)
        bus = dbus.SessionBus(mainloop=bus_loop)
        loop = gobject.MainLoop()
        self.spotify = bus.get_object("org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2")
        self.spotify.connect_to_signal("PropertiesChanged", self.track_changed)
        self.notify_id = None
        loop.run()

    def track_changed(self, interface, changed_props, invalidated_props):
        """Handle track changes."""
        metadata = changed_props.get("Metadata", {})
        if metadata:
            if pynotify.init("Spotify Notifier Demo"):

                title = unicode(metadata.get("xesam:title").encode("latin1"))
                album = unicode(metadata.get("xesam:album").encode("latin1"))
                artist = unicode(metadata.get("xesam:artist").encode("latin1"))
                
                alert = pynotify.Notification(title,
                                 "by %s from %s" % (artist, album))
                if self.notify_id:
                    alert.props.id = self.notify_id
                alert.set_urgency (pynotify.URGENCY_NORMAL)
                alert.show()
                self.notify_id = alert.props.id

if __name__ == "__main__":
    SpotifyNotifier()

Note this notification demo assumes spotify is running to set-up the listener, so it’s not something you can just set running at start-up unless you use it to wrap the start-up of spotify.

Spotify Notifications of Track Changes

Both scripts are BSD licensed so feel free to use and adapt to your own purposes.

Post Tools

  • Pingback: Ubuntu: Spotify Screensaver Toggle by Stuart Colville

  • http://muffinresearch.co.uk Stuart Colville

    Update: Fixed the basis on which the flag is set for was_playing.

  • http://twitter.com/stevenixon Steve

    By modifying the relevant section as so:

    def start_listen(self):
            """Listen to screensaver ActiveChanged events from dbus."""
            screensaver = self.bus.get_object('org.kde.screensaver',
                                                      '/ScreenSaver')
            screensaver.connect_to_signal("ActiveChanged", self.play_pause)
    

    Then it works with KDE too.

    Cheers,

    Steve

  • http://twitter.com/stevenixon Steve

    I’ve modified your notification script for KDE too:

    
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    """
    Title: Spotify Notification Demo
    Author: Stuart Colville, http://muffinresearch.co.uk
    License: BSD
    
    """
    
    import dbus
    import gobject
    from dbus.mainloop.glib import DBusGMainLoop
    import pynotify
    
    class SpotifyNotifier(object):
    
        def __init__(self):
            """initialise."""
            bus_loop = DBusGMainLoop(set_as_default=True)
            bus = dbus.SessionBus(mainloop=bus_loop)
            loop = gobject.MainLoop()
            self.spotify = bus.get_object("org.mpris.spotify", "/")
            self.spotify.connect_to_signal("TrackChange", self.track_changed)
            self.notify_id = None
            loop.run()
    
        def track_changed(self, metadata):
            """Handle track changes."""
            if metadata:
                if pynotify.init("Spotify Notifier Demo"):
    
                    title = unicode(metadata.get("title").encode("latin-1"), "utf-8")
                    album = unicode(metadata.get("album").encode("latin-1"), "utf-8")
                    artist = unicode(metadata.get("artist").encode("latin-1"), "utf-8")
    
                    knotify = dbus.SessionBus().get_object("org.kde.knotify", "/Notify")
    
                    knotify.event("warning", "kde", [], title, u"by %s from %s" % (artist, album), [], [], 0, 0, dbus_interface="org.kde.KNotify")
    
    if __name__ == "__main__":
        SpotifyNotifier()
    

    Cheers,

    Steve

  • http://muffinresearch.co.uk Stuart Colville

    @Steve: Nice work, thanks! Seems it would be nice to be able to handle KDE or Gnome in a single script. I’ll look into doing that.

    Re: the notification example, if you’re notifying via D-bus then the pynotify parts are redundant. In fact I should change the example to use D-bus nofications and avoid the extra dependency.

  • http://twitter.com/stevenixon Steve

    aye, I realized that after I had posted the script. I’ve gone through and removed them here, but hadn’t reposted as it was only redundancy.

    One problem is that the notifications stay in the KNotify area indefinitely. They’re not on show, but if you click the ! then they are all there. Looking at using knotify.update to fix, will post here if I get anywhere.

    I’d have modified it to do both gnome and KDE, but my knowledge is very basic. This is the first time I’ve really delved into the D-bus arena!

  • http://muffinresearch.co.uk Stuart Colville

    Scripts updated for MPRIS2 (rev9) with the release of Spotify v0.4.8.306.xxx

    In a nutshell, there’s some changes to the namespaces used.
    TrackChanged is gone (was part of the partial MPRIS 1 support) and hasn’t been replaced with PropertiesChanged as per http://www.mpris.org/2.0/spec/

    Use rev 8 if you are still using the older version (0.4.8), in which case the notification example will still work.

  • http://muffinresearch.co.uk Stuart Colville

    As noted above the track change notify example has been updated to use the PropertiesChanged signal which is there, it’s just not visible in D-feet. :/

    This now works for the current version of the linux spotify client.

  • Pingback: Linux: Spotify Track Notifier with added D-Bus love by Stuart Colville

  • Pingback: Linux: Spotify Screensaver D-Bus Script update by Stuart Colville

Insert a tab character in vim when expand tabs is on|(0)

I have vim set-up to use spaces in place of tabs. Sometimes you need to use an actual tab e.g. editing a Makefile. Now whilst it’s possible to change settings so that tabs are used for specific files, a quick tip to remember is to simply type in insert mode:

Ctrl+v tab

That is Ctrl and “V” and hit the tab key, et voila you’ve entered an actual tab.

GNU screen: open tab in current working directory|(1)

A nice trick for having screen open a new tab in the same directory as the one you’re currently in. To use it add it to your .screenrc

# Open new window in current dir.
bind c stuff "screen -X chdir \$PWD;screen^M"
bind ^c stuff "screen -X chdir \$PWD;screen^M"

Hat tip: mteckert on SuperUser.com

Photos on Flickr

© Copyright 2004-13 Stuart Colville, all rights reserved. May contain traces of Muffin. Powered by WordPress. Hosting by Slicehost.com This page was baked in 0.518s.