diff -Nru mpdris2-0.3/ChangeLog mpdris2-0.4/ChangeLog --- mpdris2-0.3/ChangeLog 2012-02-05 11:57:26.000000000 +0000 +++ mpdris2-0.4/ChangeLog 2013-02-02 13:45:38.000000000 +0000 @@ -0,0 +1,256 @@ +2013-02-02 Jean-Philippe Braun + + Bump for 0.4 release + +2013-01-13 Jean-Philippe Braun + + Change mpDris2 tarballs location + +2013-01-06 Jean-Philippe Braun + + Update CanGoNext property when needed + +2013-01-06 Mantas Mikulėnas + + Re-register media keys when gnome-settings-daemon is restarted + +2013-01-02 Jean-Philippe Braun + + Handle deconnection in idle mode (#35) + +2013-01-02 Mantas Mikulėnas + + Add .mailmap for `git shortlog` + python-mpd 0.2.1 support (#36) + Automatically exit when another instance is launched + +2012-12-30 Jean-Philippe Braun + + Follow PEP8 for coding style + +2012-12-29 Mantas Mikulėnas + + Cache mpd status for 2 seconds, when many properties are being queried + Use idle mode instead of polling + Support LoopStatus=Track + Accept non-file:// URLs for music library path + +2012-12-28 Mantas Mikulėnas + + Clean up format_metadata() + Support multiple artists + +2012-12-28 Mantas Mikulėnas + + Minor changes + +2012-12-28 Mantas Mikulėnas + + Change README to a symlink, to avoid duplicate files + +2012-11-30 Mantas Mikulėnas + + Include all artists in notifications + Allow multiple artists in metadata + +2012-11-24 Jean-Philippe Braun + + Smarter cover detection + +2012-10-28 Jean-Philippe Braun + + Add error message when music_dir does not exists + +2012-10-26 Simon McVittie + + Do not distribute mpDris2 in tarballs + Follow the XDG Base Directory spec, don't just assume ~/.config + Regenerate edited files if the source file changes + +2012-10-14 Jean-Philippe Braun + + Bump for 0.3 release + Update documentation + Use a signed int value if trackNumber is long (#21) + +2012-08-14 Jean-Philippe Braun + + Update metadata when replacing the queue + +2012-07-06 Mantas Mikulėnas + + Export static Rate properties + Use correct type for mpris:trackid + Use correct type for Volume + +2012-06-28 Jean-Philippe Braun + + Updated documentation + +2012-06-25 Jean-Philippe Braun + + Handle tracks where track and disc metadata are lists + Correclty set urlhandlers + +2012-06-23 Jean-Philippe Braun + + Bump for 0.2 release + +2012-02-17 Jean-Philippe Braun + + Check that music_dir path is not None + +2012-02-15 Jean-Philippe Braun + + Check music_dir existence + +2012-02-07 Jean-Philippe Braun + + Correctly check the status metadata + +2012-02-06 Jean-Philippe Braun + + Add options in mpDris2.conf + Catch connection errors for all MPD client methods + +2012-02-05 Jean-Philippe Braun + + Remove trailing spaces + Update git ignore file + Add music_dir option in the config file + Add gettext support + +2012-01-30 Jean-Philippe Braun + + Handle cover.png album art + +2012-01-20 Mantas Mikulėnas + + build: honor $NOCONFIGURE, correctly forward arguments otherwise + +2012-01-15 Jean-Philippe Braun + + Add a command line option to run in debug mode + +2012-01-11 Jean-Philippe Braun + + Ensure title and artist exists + +2012-01-10 Jean-Philippe Braun + + Return correct dbus type for the Position property + +2011-12-23 Jean-Philippe Braun + + Show the (re)connected notification only after a disconnection (closes #12) + Be sure to have a songid (ie: playing or paused state) for sending the Seeked signal + Honor notify = False setting + +2011-12-23 Mantas Mikulėnas + + Fix password@host parsing mixup. + +2011-12-19 Jean-Philippe Braun + + Fix configuration path + Don't log connection errors indefinitely if network is down + Let MPDWrapper handle multimedia keys + +2011-12-18 Jean-Philippe Braun + + Release/Aquire name on bus on deconnection/connection + Handle network connections/deconnections gracefully + Minor change to README + Use MPDWrapper instead of MPDClient directly in a OO way + +2011-12-17 Jean-Philippe Braun + + Add README + Run automatically configure + Add missing files for automake + Add mpDris to autostart dir on installation + Automake packaging + Offset is in microseconds + Use python logger for messages + Remove unused variables and import + Send the Seeked signal if the position is changed from another client + Fix usage display + +2011-12-16 Mantas Mikulėnas + + remove obsolete MPRISv1 constants + whitespace consistency, updated error messages + +2011-12-16 Jean-Philippe Braun + + Fix crash when playing long songs (closes #5) + +2011-12-16 Mantas Mikulėnas + + Return correct playback position + +2011-12-14 Jean-Philippe Braun + + Correctly set the position of the current song in microseconds + Send also the Seeked signal + +2011-12-04 Mantas Mikulėnas + + notify: adjust notification body + notify: fix display of coverart + config: remove configuration note from source + config: update example + +2011-12-03 Mantas Mikulėnas + + config: correctly parse boolean params + +2011-12-02 Jean-Philippe Braun + + Add mpris2.conf example + Don't fail if pynotify is not installed + +2011-12-01 Hakan Erduman + + Notification with libnotify + Subscribe to gnome multimedia keys + +2011-11-07 Jean-Philippe Braun + + Do not search covers for non local files (closes #6) + +2011-10-31 Mantas Mikulėnas + + Display correct path on startup + +2011-10-28 Mantas Mikulėnas + + Try to automatically find music directory + Fix 'Identity' to match actual name + +2011-10-28 Mantas Mikulėnas + + Add a DesktopEntry for Ubuntu Sound Menu + +2011-08-20 Jean-Philippe Braun + + Return time = 0 when mpd is stopped + +2011-08-14 Jean-Philippe Braun + + Length should be in micro seconds + +2011-08-13 Jean-Philippe Braun + + Add some basic local cover support + +2011-07-07 Jean-Philippe Braun + + Check track number match + +2011-06-10 Jean-Philippe Braun + + Check if time length is available + Use of global music_dir param fix configuration file path + Update copyright info + Initial import diff -Nru mpdris2-0.3/INSTALL mpdris2-0.4/INSTALL --- mpdris2-0.3/INSTALL 2012-06-28 19:08:11.000000000 +0000 +++ mpdris2-0.4/INSTALL 2012-12-30 13:07:14.000000000 +0000 @@ -1,8 +1,8 @@ Installation Instructions ************************* -Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, -2006, 2007, 2008, 2009 Free Software Foundation, Inc. +Copyright (C) 1994-1996, 1999-2002, 2004-2012 Free Software Foundation, +Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright @@ -226,6 +226,11 @@ and if that doesn't work, install pre-built binaries of GCC for HP-UX. + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot parse its `' header file. The option `-nodtk' can be used as a workaround. If GNU CC is not installed, it is therefore recommended @@ -304,9 +309,10 @@ overridden in the site shell script). Unfortunately, this technique does not work for `CONFIG_SHELL' due to -an Autoconf bug. Until the bug is fixed you can use this workaround: +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: - CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash `configure' Invocation ====================== @@ -362,4 +368,3 @@ `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. - diff -Nru mpdris2-0.3/Makefile.in mpdris2-0.4/Makefile.in --- mpdris2-0.3/Makefile.in 2012-10-14 08:50:47.000000000 +0000 +++ mpdris2-0.4/Makefile.in 2013-02-02 13:47:02.000000000 +0000 @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.11.6 from Makefile.am. +# Makefile.in generated by automake 1.11.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -16,23 +16,6 @@ @SET_MAKE@ VPATH = @srcdir@ -am__make_dryrun = \ - { \ - am__dry=no; \ - case $$MAKEFLAGS in \ - *\\[\ \ ]*) \ - echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ - | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ - *) \ - for am__flg in $$MAKEFLAGS; do \ - case $$am__flg in \ - *=*|--*) ;; \ - *n*) am__dry=yes; break;; \ - esac; \ - done;; \ - esac; \ - test $$am__dry = yes; \ - } pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ @@ -72,11 +55,6 @@ install-pdf-recursive install-ps-recursive install-recursive \ installcheck-recursive installdirs-recursive pdf-recursive \ ps-recursive uninstall-recursive -am__can_run_installinfo = \ - case $$AM_UPDATE_INFO_DIR in \ - n|no|NO) false;; \ - *) (install-info --version) >/dev/null 2>&1;; \ - esac am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ @@ -157,7 +135,6 @@ ACLOCAL = @ACLOCAL@ ALL_LINGUAS = @ALL_LINGUAS@ AMTAR = @AMTAR@ -AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ @@ -185,10 +162,6 @@ INTLTOOL_MERGE = @INTLTOOL_MERGE@ INTLTOOL_PERL = @INTLTOOL_PERL@ INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ -INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ -INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ -INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ -INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ @@ -240,8 +213,6 @@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ -intltool__v_merge_options_ = @intltool__v_merge_options_@ -intltool__v_merge_options_0 = @intltool__v_merge_options_0@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ @@ -308,11 +279,8 @@ $(am__aclocal_m4_deps): install-dist_docDATA: $(dist_doc_DATA) @$(NORMAL_INSTALL) + test -z "$(docdir)" || $(MKDIR_P) "$(DESTDIR)$(docdir)" @list='$(dist_doc_DATA)'; test -n "$(docdir)" || list=; \ - if test -n "$$list"; then \ - echo " $(MKDIR_P) '$(DESTDIR)$(docdir)'"; \ - $(MKDIR_P) "$(DESTDIR)$(docdir)" || exit 1; \ - fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ @@ -497,10 +465,13 @@ done @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ - $(am__make_dryrun) \ - || test -d "$(distdir)/$$subdir" \ - || $(MKDIR_P) "$(distdir)/$$subdir" \ - || exit 1; \ + test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ $(am__relativize); \ new_distdir=$$reldir; \ @@ -589,7 +560,7 @@ *.zip*) \ unzip $(distdir).zip ;;\ esac - chmod -R a-w $(distdir); chmod u+w $(distdir) + chmod -R a-w $(distdir); chmod a+w $(distdir) mkdir $(distdir)/_build mkdir $(distdir)/_inst chmod a-w $(distdir) diff -Nru mpdris2-0.3/README mpdris2-0.4/README --- mpdris2-0.3/README 2012-06-28 19:18:48.000000000 +0000 +++ mpdris2-0.4/README 2013-02-02 13:21:32.000000000 +0000 @@ -1,40 +1,54 @@ # mpDris2 -mpDris2 provide MPRIS 2 support to mpd (Music Play Daemon). +mpDris2 provide MPRIS 2 support to mpd (Music Player Daemon). mpDris2 is run in the user session and monitors a local or distant mpd server. # Installation - git clone git://github.com/eonpatapon/mpDris2.git - cd mpDris2 - ./autogen.sh - ./configure --sysconfdir=/etc - make install (as root) +## Stable release + +Download the latest release at http://mpdris2.patapon.info/latest/ + + tar zvxf mpDris2-X.X.tar.gz + cd mpDris2-X.X + ./autogen.sh --sysconfdir=/etc + make install (as root) + +Older releases can be found at http://mpdris2.patapon.info/ + +## From git + + git clone git://github.com/eonpatapon/mpDris2.git + cd mpDris2 + ./autogen.sh --sysconfdir=/etc + make install (as root) Logout/login from your session. -Default prefix is /usr/local. +Default prefix is ``/usr/local``. # Configuration By default, mpDris2 will try to connect to localhost:6600. -To set a different host or port copy the example configuration file -/usr/[local]/share/doc/mpdris2/mpDris2.conf to ~/.config/mpDris2/mpDris2.conf. +To set a different host or port copy the example configuration file +``/usr/[local]/share/doc/mpdris2/mpDris2.conf`` to ``~/.config/mpDris2/mpDris2.conf``. + +Use the configuration to enable notifications and multimedia keys support (on +the GNOME desktop). -Use the configuration to enable notifications and multimedia keys support. -You need also to set the 'music_dir' option for mpDris2 to export covers +You need also to set the ``music_dir`` option for mpDris2 to export covers paths in the MPRIS metadata. Restart your session or mpDris2 after changing mpDris2.conf. ## Sample configuration - [Connection] - host = 192.168.1.5 - port = 6600 - music_dir = /media/music/ - - [Bling] - notify = False - mmkeys = True + [Connection] + host = 192.168.1.5 + port = 6600 + music_dir = /media/music/ + + [Bling] + notify = False + mmkeys = True diff -Nru mpdris2-0.3/aclocal.m4 mpdris2-0.4/aclocal.m4 --- mpdris2-0.3/aclocal.m4 2012-10-14 08:50:46.000000000 +0000 +++ mpdris2-0.4/aclocal.m4 2013-02-02 13:47:02.000000000 +0000 @@ -1,4 +1,4 @@ -# generated automatically by aclocal 1.11.6 -*- Autoconf -*- +# generated automatically by aclocal 1.11.3 -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, # 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, @@ -14,15 +14,15 @@ m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl -m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],, -[m4_warning([this file was generated for autoconf 2.69. +m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.68],, +[m4_warning([this file was generated for autoconf 2.68. You have another version of autoconf. It may work, but is not guaranteed to. If you have problems, you may need to regenerate the build system entirely. To do so, use the procedure documented by the package, typically `autoreconf'.])]) dnl IT_PROG_INTLTOOL([MINIMUM-VERSION], [no-xml]) -# serial 42 IT_PROG_INTLTOOL +# serial 40 IT_PROG_INTLTOOL AC_DEFUN([IT_PROG_INTLTOOL], [ AC_PREREQ([2.50])dnl AC_REQUIRE([AM_NLS])dnl @@ -35,11 +35,13 @@ ;; esac -INTLTOOL_REQUIRED_VERSION_AS_INT=`echo $1 | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'` -INTLTOOL_APPLIED_VERSION=`intltool-update --version | head -1 | cut -d" " -f3` -INTLTOOL_APPLIED_VERSION_AS_INT=`echo $INTLTOOL_APPLIED_VERSION | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'` if test -n "$1"; then AC_MSG_CHECKING([for intltool >= $1]) + + INTLTOOL_REQUIRED_VERSION_AS_INT=`echo $1 | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'` + INTLTOOL_APPLIED_VERSION=`intltool-update --version | head -1 | cut -d" " -f3` + [INTLTOOL_APPLIED_VERSION_AS_INT=`echo $INTLTOOL_APPLIED_VERSION | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'` + ] AC_MSG_RESULT([$INTLTOOL_APPLIED_VERSION found]) test "$INTLTOOL_APPLIED_VERSION_AS_INT" -ge "$INTLTOOL_REQUIRED_VERSION_AS_INT" || AC_MSG_ERROR([Your intltool is too old. You need intltool $1 or later.]) @@ -52,48 +54,25 @@ AC_MSG_ERROR([The intltool scripts were not found. Please install intltool.]) fi -if test -z "$AM_DEFAULT_VERBOSITY"; then - AM_DEFAULT_VERBOSITY=1 -fi -AC_SUBST([AM_DEFAULT_VERBOSITY]) - -INTLTOOL_V_MERGE='$(INTLTOOL__v_MERGE_$(V))' -INTLTOOL__v_MERGE_='$(INTLTOOL__v_MERGE_$(AM_DEFAULT_VERBOSITY))' -INTLTOOL__v_MERGE_0='@echo " ITMRG " [$]@;' -AC_SUBST(INTLTOOL_V_MERGE) -AC_SUBST(INTLTOOL__v_MERGE_) -AC_SUBST(INTLTOOL__v_MERGE_0) - -INTLTOOL_V_MERGE_OPTIONS='$(intltool__v_merge_options_$(V))' -intltool__v_merge_options_='$(intltool__v_merge_options_$(AM_DEFAULT_VERBOSITY))' -intltool__v_merge_options_0='-q' -AC_SUBST(INTLTOOL_V_MERGE_OPTIONS) -AC_SUBST(intltool__v_merge_options_) -AC_SUBST(intltool__v_merge_options_0) - - INTLTOOL_DESKTOP_RULE='%.desktop: %.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' -INTLTOOL_DIRECTORY_RULE='%.directory: %.directory.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_KEYS_RULE='%.keys: %.keys.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -k -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_PROP_RULE='%.prop: %.prop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_OAF_RULE='%.oaf: %.oaf.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -o -p $(top_srcdir)/po $< [$]@' - INTLTOOL_PONG_RULE='%.pong: %.pong.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_SERVER_RULE='%.server: %.server.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -o -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_SHEET_RULE='%.sheet: %.sheet.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' -INTLTOOL_SOUNDLIST_RULE='%.soundlist: %.soundlist.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_UI_RULE='%.ui: %.ui.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_XML_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' -if test "$INTLTOOL_APPLIED_VERSION_AS_INT" -ge 5000; then - INTLTOOL_XML_NOMERGE_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u --no-translations $< [$]@' -else - INTLTOOL_XML_NOMERGE_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) ; $(INTLTOOL_V_MERGE)_it_tmp_dir=tmp.intltool.[$][$]RANDOM && mkdir [$][$]_it_tmp_dir && LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u [$][$]_it_tmp_dir $< [$]@ && rmdir [$][$]_it_tmp_dir' -fi - INTLTOOL_XAM_RULE='%.xam: %.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_KBD_RULE='%.kbd: %.kbd.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -m -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_CAVES_RULE='%.caves: %.caves.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_SCHEMAS_RULE='%.schemas: %.schemas.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -s -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_THEME_RULE='%.theme: %.theme.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_SERVICE_RULE='%.service: %.service.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' - INTLTOOL_POLICY_RULE='%.policy: %.policy.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_DESKTOP_RULE='%.desktop: %.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' +INTLTOOL_DIRECTORY_RULE='%.directory: %.directory.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_KEYS_RULE='%.keys: %.keys.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -k -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_PROP_RULE='%.prop: %.prop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_OAF_RULE='%.oaf: %.oaf.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -o -p $(top_srcdir)/po $< [$]@' + INTLTOOL_PONG_RULE='%.pong: %.pong.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_SERVER_RULE='%.server: %.server.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -o -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_SHEET_RULE='%.sheet: %.sheet.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' +INTLTOOL_SOUNDLIST_RULE='%.soundlist: %.soundlist.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_UI_RULE='%.ui: %.ui.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_XML_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_XML_NOMERGE_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u --no-translations $< [$]@' + INTLTOOL_XAM_RULE='%.xam: %.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_KBD_RULE='%.kbd: %.kbd.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -m -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_CAVES_RULE='%.caves: %.caves.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_SCHEMAS_RULE='%.schemas: %.schemas.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -s -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_THEME_RULE='%.theme: %.theme.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_SERVICE_RULE='%.service: %.service.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' + INTLTOOL_POLICY_RULE='%.policy: %.policy.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' _IT_SUBST(INTLTOOL_DESKTOP_RULE) _IT_SUBST(INTLTOOL_DIRECTORY_RULE) @@ -286,7 +265,7 @@ [am__api_version='1.11' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.11.6], [], +m4_if([$1], [1.11.3], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -302,7 +281,7 @@ # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.11.6])dnl +[AM_AUTOMAKE_VERSION([1.11.3])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) diff -Nru mpdris2-0.3/configure mpdris2-0.4/configure --- mpdris2-0.3/configure 2012-10-14 08:50:47.000000000 +0000 +++ mpdris2-0.4/configure 2013-02-02 13:47:02.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for mpDris2 0.3. +# Generated by GNU Autoconf 2.69 for mpDris2 0.4. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -576,8 +576,8 @@ # Identity of this package. PACKAGE_NAME='mpDris2' PACKAGE_TARNAME='mpdris2' -PACKAGE_VERSION='0.3' -PACKAGE_STRING='mpDris2 0.3' +PACKAGE_VERSION='0.4' +PACKAGE_STRING='mpDris2 0.4' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -628,13 +628,6 @@ INTLTOOL_KEYS_RULE INTLTOOL_DIRECTORY_RULE INTLTOOL_DESKTOP_RULE -intltool__v_merge_options_0 -intltool__v_merge_options_ -INTLTOOL_V_MERGE_OPTIONS -INTLTOOL__v_MERGE_0 -INTLTOOL__v_MERGE_ -INTLTOOL_V_MERGE -AM_DEFAULT_VERBOSITY INTLTOOL_EXTRACT INTLTOOL_MERGE INTLTOOL_UPDATE @@ -1265,7 +1258,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures mpDris2 0.3 to adapt to many kinds of systems. +\`configure' configures mpDris2 0.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1331,7 +1324,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of mpDris2 0.3:";; + short | recursive ) echo "Configuration of mpDris2 0.4:";; esac cat <<\_ACEOF @@ -1419,7 +1412,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -mpDris2 configure 0.3 +mpDris2 configure 0.4 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1587,7 +1580,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by mpDris2 $as_me 0.3, which was +It was created by mpDris2 $as_me 0.4, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2402,7 +2395,7 @@ # Define the identity of the package. PACKAGE=mpDris2 - VERSION=0.3 + VERSION=0.4 # Some tools Automake needs. @@ -3671,12 +3664,14 @@ ;; esac -INTLTOOL_REQUIRED_VERSION_AS_INT=`echo 0.26 | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'` -INTLTOOL_APPLIED_VERSION=`intltool-update --version | head -1 | cut -d" " -f3` -INTLTOOL_APPLIED_VERSION_AS_INT=`echo $INTLTOOL_APPLIED_VERSION | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'` if test -n "0.26"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for intltool >= 0.26" >&5 $as_echo_n "checking for intltool >= 0.26... " >&6; } + + INTLTOOL_REQUIRED_VERSION_AS_INT=`echo 0.26 | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'` + INTLTOOL_APPLIED_VERSION=`intltool-update --version | head -1 | cut -d" " -f3` + INTLTOOL_APPLIED_VERSION_AS_INT=`echo $INTLTOOL_APPLIED_VERSION | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'` + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $INTLTOOL_APPLIED_VERSION found" >&5 $as_echo "$INTLTOOL_APPLIED_VERSION found" >&6; } test "$INTLTOOL_APPLIED_VERSION_AS_INT" -ge "$INTLTOOL_REQUIRED_VERSION_AS_INT" || @@ -3807,48 +3802,25 @@ as_fn_error $? "The intltool scripts were not found. Please install intltool." "$LINENO" 5 fi -if test -z "$AM_DEFAULT_VERBOSITY"; then - AM_DEFAULT_VERBOSITY=1 -fi - - -INTLTOOL_V_MERGE='$(INTLTOOL__v_MERGE_$(V))' -INTLTOOL__v_MERGE_='$(INTLTOOL__v_MERGE_$(AM_DEFAULT_VERBOSITY))' -INTLTOOL__v_MERGE_0='@echo " ITMRG " $@;' - - - - -INTLTOOL_V_MERGE_OPTIONS='$(intltool__v_merge_options_$(V))' -intltool__v_merge_options_='$(intltool__v_merge_options_$(AM_DEFAULT_VERBOSITY))' -intltool__v_merge_options_0='-q' - - - - - INTLTOOL_DESKTOP_RULE='%.desktop: %.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' -INTLTOOL_DIRECTORY_RULE='%.directory: %.directory.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_KEYS_RULE='%.keys: %.keys.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -k -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_PROP_RULE='%.prop: %.prop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_OAF_RULE='%.oaf: %.oaf.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -o -p $(top_srcdir)/po $< $@' - INTLTOOL_PONG_RULE='%.pong: %.pong.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_SERVER_RULE='%.server: %.server.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -o -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_SHEET_RULE='%.sheet: %.sheet.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' -INTLTOOL_SOUNDLIST_RULE='%.soundlist: %.soundlist.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_UI_RULE='%.ui: %.ui.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_XML_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' -if test "$INTLTOOL_APPLIED_VERSION_AS_INT" -ge 5000; then - INTLTOOL_XML_NOMERGE_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u --no-translations $< $@' -else - INTLTOOL_XML_NOMERGE_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) ; $(INTLTOOL_V_MERGE)_it_tmp_dir=tmp.intltool.$$RANDOM && mkdir $$_it_tmp_dir && LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u $$_it_tmp_dir $< $@ && rmdir $$_it_tmp_dir' -fi - INTLTOOL_XAM_RULE='%.xam: %.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_KBD_RULE='%.kbd: %.kbd.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -m -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_CAVES_RULE='%.caves: %.caves.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_SCHEMAS_RULE='%.schemas: %.schemas.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -s -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_THEME_RULE='%.theme: %.theme.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_SERVICE_RULE='%.service: %.service.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' - INTLTOOL_POLICY_RULE='%.policy: %.policy.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(INTLTOOL_V_MERGE)LC_ALL=C $(INTLTOOL_MERGE) $(INTLTOOL_V_MERGE_OPTIONS) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_DESKTOP_RULE='%.desktop: %.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' +INTLTOOL_DIRECTORY_RULE='%.directory: %.directory.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_KEYS_RULE='%.keys: %.keys.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -k -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_PROP_RULE='%.prop: %.prop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_OAF_RULE='%.oaf: %.oaf.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -o -p $(top_srcdir)/po $< $@' + INTLTOOL_PONG_RULE='%.pong: %.pong.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_SERVER_RULE='%.server: %.server.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -o -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_SHEET_RULE='%.sheet: %.sheet.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' +INTLTOOL_SOUNDLIST_RULE='%.soundlist: %.soundlist.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_UI_RULE='%.ui: %.ui.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_XML_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_XML_NOMERGE_RULE='%.xml: %.xml.in $(INTLTOOL_MERGE) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u --no-translations $< $@' + INTLTOOL_XAM_RULE='%.xam: %.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_KBD_RULE='%.kbd: %.kbd.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -m -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_CAVES_RULE='%.caves: %.caves.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_SCHEMAS_RULE='%.schemas: %.schemas.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -s -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_THEME_RULE='%.theme: %.theme.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_SERVICE_RULE='%.service: %.service.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' + INTLTOOL_POLICY_RULE='%.policy: %.policy.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@' @@ -4790,7 +4762,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by mpDris2 $as_me 0.3, which was +This file was extended by mpDris2 $as_me 0.4, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -4847,7 +4819,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -mpDris2 config.status 0.3 +mpDris2 config.status 0.4 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff -Nru mpdris2-0.3/configure.ac mpdris2-0.4/configure.ac --- mpdris2-0.3/configure.ac 2012-10-14 08:47:36.000000000 +0000 +++ mpdris2-0.4/configure.ac 2013-02-02 13:43:25.000000000 +0000 @@ -1,5 +1,5 @@ -AC_INIT(mpDris2, 0.3) -AM_INIT_AUTOMAKE(mpDris2, 0.3, [https://github.com/eonpatapon/mpDris2]) +AC_INIT(mpDris2, 0.4) +AM_INIT_AUTOMAKE(mpDris2, 0.4, [https://github.com/eonpatapon/mpDris2]) AM_PATH_PYTHON([2.5],, [:]) GETTEXT_PACKAGE=mpDris2 AC_SUBST(GETTEXT_PACKAGE) diff -Nru mpdris2-0.3/debian/changelog mpdris2-0.4/debian/changelog --- mpdris2-0.3/debian/changelog 2012-11-16 13:15:08.000000000 +0000 +++ mpdris2-0.4/debian/changelog 2013-04-29 08:35:06.000000000 +0000 @@ -1,3 +1,14 @@ +mpdris2 (0.4-1) unstable; urgency=low + + * New upstream version + - 0001-Regenerate-edited-files...: remove, applied upstream + - 0002-Follow-the-XDG-Base-Directory-spec...: remove, applied upstream + - 0003-Put-example-configuration-in...: refresh + * Add patches (applied upstream for 0.5) to put additional metadata on + notifications for better GNOME Shell 3.8 integration + + -- Simon McVittie Mon, 29 Apr 2013 09:32:54 +0100 + mpdris2 (0.3-2) unstable; urgency=low * Build-depend on intltool (patch from Colin Watson, Closes: #693431) diff -Nru mpdris2-0.3/debian/mpdris2.maintscript mpdris2-0.4/debian/mpdris2.maintscript --- mpdris2-0.3/debian/mpdris2.maintscript 1970-01-01 00:00:00.000000000 +0000 +++ mpdris2-0.4/debian/mpdris2.maintscript 2013-04-29 08:35:06.000000000 +0000 @@ -0,0 +1 @@ +mv_conffile /etc/xdg/autostart/mpDris2.desktop /etc/xdg/autostart/mpdris2.desktop 0.4~ mpdris2 diff -Nru mpdris2-0.3/debian/patches/0001-Regenerate-edited-files-if-the-source-file-changes.patch mpdris2-0.4/debian/patches/0001-Regenerate-edited-files-if-the-source-file-changes.patch --- mpdris2-0.3/debian/patches/0001-Regenerate-edited-files-if-the-source-file-changes.patch 2012-11-16 13:15:08.000000000 +0000 +++ mpdris2-0.4/debian/patches/0001-Regenerate-edited-files-if-the-source-file-changes.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -From 48d8efac28c87167e4efc08282b6683266baa3ae Mon Sep 17 00:00:00 2001 -From: Simon McVittie -Date: Thu, 13 Sep 2012 09:54:35 +0100 -Subject: [PATCH 1/3] Regenerate edited files if the source file changes - ---- - src/Makefile.am | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/Makefile.am b/src/Makefile.am -index 755e3a2..4916eef 100644 ---- a/src/Makefile.am -+++ b/src/Makefile.am -@@ -14,7 +14,7 @@ CLEANFILES = org.mpris.MediaPlayer2.mpd.service mpDris2.py mpDris2 - edit = sed -e 's|@bindir[@]|$(bindir)|g' \ - -e 's|@datadir[@]|$(datadir)|g' - --mpDris2: Makefile -+mpDris2: mpDris2.in Makefile - rm -f $@ $@.tmp - $(MKDIR_P) $(@D) - srcdir='' -@@ -23,7 +23,7 @@ mpDris2: Makefile - mv $@.tmp $@ - ln -sf $@ $@.py - --org.mpris.MediaPlayer2.mpd.service: Makefile -+org.mpris.MediaPlayer2.mpd.service: org.mpris.MediaPlayer2.mpd.service.in Makefile - rm -f $@ $@.tmp - $(MKDIR_P) $(@D) - srcdir='' --- -1.7.10.4 - diff -Nru mpdris2-0.3/debian/patches/0001-Rename-desktop-entry-to-all-lower-case.patch mpdris2-0.4/debian/patches/0001-Rename-desktop-entry-to-all-lower-case.patch --- mpdris2-0.3/debian/patches/0001-Rename-desktop-entry-to-all-lower-case.patch 1970-01-01 00:00:00.000000000 +0000 +++ mpdris2-0.4/debian/patches/0001-Rename-desktop-entry-to-all-lower-case.patch 2013-04-29 08:35:06.000000000 +0000 @@ -0,0 +1,80 @@ +From: Simon McVittie +Date: Mon, 22 Apr 2013 21:37:27 +0100 +Subject: Rename desktop entry to all-lower-case + +GNOME Shell appears to normalize desktop entry IDs (basenames) +to all-lower-case. I have no idea whether this is standard, +but if we consistently use lower-case we won't have to +worry about it. + +Bug: https://github.com/eonpatapon/mpDris2/pull/46 +Applied-upstream: 0.5, commit:15723e27ca8ab59a8e744a88de02f0d2fef742bb +--- + src/Makefile.am | 4 ++-- + src/mpDris2.desktop | 10 ---------- + src/mpDris2.in | 2 +- + src/mpdris2.desktop | 10 ++++++++++ + 4 files changed, 13 insertions(+), 13 deletions(-) + delete mode 100644 src/mpDris2.desktop + create mode 100644 src/mpdris2.desktop + +diff --git a/src/Makefile.am b/src/Makefile.am +index b842d7f..a009560 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -3,8 +3,8 @@ dbusdir = ${datadir}/dbus-1/services/ + autostartdir = ${sysconfdir}/xdg/autostart/ + + bin_SCRIPTS = mpDris2 +-dist_desktop_DATA = mpDris2.desktop +-autostart_DATA = mpDris2.desktop ++dist_desktop_DATA = mpdris2.desktop ++autostart_DATA = mpdris2.desktop + dist_doc_DATA = mpDris2.conf + nodist_dbus_DATA = org.mpris.MediaPlayer2.mpd.service + +diff --git a/src/mpDris2.desktop b/src/mpDris2.desktop +deleted file mode 100644 +index 977ca29..0000000 +--- a/src/mpDris2.desktop ++++ /dev/null +@@ -1,10 +0,0 @@ +-[Desktop Entry] +-Name=Music Player Daemon +-Comment=MPRIS 2 support for MPD +-Exec=mpDris2 +-Icon=sound +-StartupNotify=false +-Type=Application +-Categories=Audio;Music;Player; +-NoDisplay=true +-X-DBUS-ServiceName=org.mpris.MediaPlayer2.mpd +diff --git a/src/mpDris2.in b/src/mpDris2.in +index 7d3daff..1639efb 100755 +--- a/src/mpDris2.in ++++ b/src/mpDris2.in +@@ -658,7 +658,7 @@ class MPRISInterface(dbus.service.Object): + __root_props = { + "CanQuit": (False, None), + "CanRaise": (False, None), +- "DesktopEntry": ("mpDris2", None), ++ "DesktopEntry": ("mpdris2", None), + "HasTrackList": (False, None), + "Identity": (identity, None), + "SupportedUriSchemes": (dbus.Array(signature="s"), None), +diff --git a/src/mpdris2.desktop b/src/mpdris2.desktop +new file mode 100644 +index 0000000..977ca29 +--- /dev/null ++++ b/src/mpdris2.desktop +@@ -0,0 +1,10 @@ ++[Desktop Entry] ++Name=Music Player Daemon ++Comment=MPRIS 2 support for MPD ++Exec=mpDris2 ++Icon=sound ++StartupNotify=false ++Type=Application ++Categories=Audio;Music;Player; ++NoDisplay=true ++X-DBUS-ServiceName=org.mpris.MediaPlayer2.mpd diff -Nru mpdris2-0.3/debian/patches/0002-Follow-the-XDG-Base-Directory-spec-don-t-just-assume.patch mpdris2-0.4/debian/patches/0002-Follow-the-XDG-Base-Directory-spec-don-t-just-assume.patch --- mpdris2-0.3/debian/patches/0002-Follow-the-XDG-Base-Directory-spec-don-t-just-assume.patch 2012-11-16 13:15:08.000000000 +0000 +++ mpdris2-0.4/debian/patches/0002-Follow-the-XDG-Base-Directory-spec-don-t-just-assume.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,79 +0,0 @@ -From 443400afc22987a9f17c3dc31afe9377542352ac Mon Sep 17 00:00:00 2001 -From: Simon McVittie -Date: Thu, 13 Sep 2012 09:54:08 +0100 -Subject: [PATCH 2/3] Follow the XDG Base Directory spec, don't just assume - ~/.config - -Part of the value of the Base Directory spec is that you can override it -with standard environment variables. ---- - src/mpDris2.in | 35 +++++++++++++++++++++++++++++------ - 1 file changed, 29 insertions(+), 6 deletions(-) - -diff --git a/src/mpDris2.in b/src/mpDris2.in -index d8b5b90..c87fa7f 100755 ---- a/src/mpDris2.in -+++ b/src/mpDris2.in -@@ -738,13 +738,37 @@ def format_metadata(metadata): - - return dbus.Dictionary(metadata, signature='sv') - -+def each_xdg_config(suffix): -+ """Return each location matching XDG_CONFIG_DIRS/suffix in descending -+ priority order. -+ """ -+ config_home = os.environ.get('XDG_CONFIG_HOME', -+ os.path.expanduser('~/.config')) -+ config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':') -+ return ([os.path.join(config_home, suffix)] + -+ [os.path.join(d, suffix) for d in config_dirs]) -+ -+def open_first_xdg_config(suffix): -+ """Try to open each location matching XDG_CONFIG_DIRS/suffix as a file. -+ Return the first that can be opened successfully, or None. -+ """ -+ for filename in each_xdg_config(suffix): -+ try: -+ f = open(filename, 'r') -+ except IOError: -+ pass -+ else: -+ return f -+ else: -+ return None -+ - def find_music_dir(): - if 'XDG_MUSIC_DIR' in os.environ: - return os.environ['XDG_MUSIC_DIR'] - -- conf = os.path.expanduser('~/.config/user-dirs.dirs') -- try: -- for line in open(conf, 'r'): -+ conf = open_first_xdg_config('user-dirs.dirs') -+ if conf is not None: -+ for line in conf: - if not line.startswith('XDG_MUSIC_DIR='): - continue - # use shlex to handle "shell escaping" -@@ -755,8 +779,6 @@ def find_music_dir(): - return path - else: - continue # other forms are not supported -- except IOError: -- pass - - paths = '~/Music', '~/music' - for path in map(os.path.expanduser, paths): -@@ -785,7 +807,8 @@ if __name__ == '__main__': - path = find_music_dir() - - config = ConfigParser.SafeConfigParser() -- config.read(['/etc/mpDris2.conf', os.path.expanduser('~/.config/mpDris2/mpDris2.conf')]) -+ config.read(['/etc/mpDris2.conf'] + -+ list(reversed(each_xdg_config('mpDris2/mpDris2.conf')))) - - if config.has_option('Connection', 'host'): - params['host'] = config.get('Connection', 'host') --- -1.7.10.4 - diff -Nru mpdris2-0.3/debian/patches/0002-Set-X-GNOME-UsesNotifications-so-we-appear-in-gnome-.patch mpdris2-0.4/debian/patches/0002-Set-X-GNOME-UsesNotifications-so-we-appear-in-gnome-.patch --- mpdris2-0.3/debian/patches/0002-Set-X-GNOME-UsesNotifications-so-we-appear-in-gnome-.patch 1970-01-01 00:00:00.000000000 +0000 +++ mpdris2-0.4/debian/patches/0002-Set-X-GNOME-UsesNotifications-so-we-appear-in-gnome-.patch 2013-04-29 08:35:06.000000000 +0000 @@ -0,0 +1,32 @@ +From: Simon McVittie +Date: Mon, 22 Apr 2013 21:33:35 +0100 +Subject: Set X-GNOME-UsesNotifications so we appear in gnome-control-center + +Bug: https://github.com/eonpatapon/mpDris2/pull/46 +Applied-upstream: 0.5, commit:f6537cf64fb6ba388064fd458ac6bf6fc4a87848 +--- + src/mpDris2.in | 1 + + src/mpdris2.desktop | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/src/mpDris2.in b/src/mpDris2.in +index 1639efb..e207278 100755 +--- a/src/mpDris2.in ++++ b/src/mpDris2.in +@@ -603,6 +603,7 @@ class Notify: + """ Issue a new notification """ + if self._notification: + self._notification = pynotify.Notification(title, body, uri) ++ self._notification.set_hint('desktop-entry', 'mpdris2') + self._notification.show() + + def rnotify(self, title, body, uri=''): +diff --git a/src/mpdris2.desktop b/src/mpdris2.desktop +index 977ca29..73af19c 100644 +--- a/src/mpdris2.desktop ++++ b/src/mpdris2.desktop +@@ -8,3 +8,4 @@ Type=Application + Categories=Audio;Music;Player; + NoDisplay=true + X-DBUS-ServiceName=org.mpris.MediaPlayer2.mpd ++X-GNOME-UsesNotifications=true diff -Nru mpdris2-0.3/debian/patches/0003-Put-example-configuration-in-u-s-d-mpdris2-examples.patch mpdris2-0.4/debian/patches/0003-Put-example-configuration-in-u-s-d-mpdris2-examples.patch --- mpdris2-0.3/debian/patches/0003-Put-example-configuration-in-u-s-d-mpdris2-examples.patch 2012-11-16 13:15:08.000000000 +0000 +++ mpdris2-0.4/debian/patches/0003-Put-example-configuration-in-u-s-d-mpdris2-examples.patch 2013-04-29 08:35:06.000000000 +0000 @@ -1,7 +1,6 @@ -From 3a7a09674f7bbf9c016aa3797b074d48d4a6dca5 Mon Sep 17 00:00:00 2001 From: Simon McVittie -Date: Fri, 26 Oct 2012 11:26:10 +0100 -Subject: [PATCH 3/3] Put example configuration in /u/s/d/mpdris2/examples +Date: Tue, 23 Apr 2013 09:35:56 +0100 +Subject: Put example configuration in /u/s/d/mpdris2/examples According to Debian policy. @@ -9,22 +8,25 @@ Forwarded: not-needed, Debian-specific --- README | 3 ++- - README.md | 3 ++- [omitted, not in tarball] src/Makefile.am | 3 ++- - 3 files changed, 6 insertions(+), 3 deletions(-) + 2 files changed, 4 insertions(+), 2 deletions(-) +diff --git a/README b/README +index 3bb308c..36bc878 100644 --- a/README +++ b/README -@@ -20,7 +20,8 @@ Default prefix is /usr/local. +@@ -32,7 +32,8 @@ Default prefix is ``/usr/local``. By default, mpDris2 will try to connect to localhost:6600. - To set a different host or port copy the example configuration file --/usr/[local]/share/doc/mpdris2/mpDris2.conf to ~/.config/mpDris2/mpDris2.conf. -+/usr/[local]/share/doc/mpdris2/examples/mpDris2.conf to -+~/.config/mpDris2/mpDris2.conf. + To set a different host or port copy the example configuration file +-``/usr/[local]/share/doc/mpdris2/mpDris2.conf`` to ``~/.config/mpDris2/mpDris2.conf``. ++``/usr/[local]/share/doc/mpdris2/examples/mpDris2.conf`` to ++``~/.config/mpDris2/mpDris2.conf``. - Use the configuration to enable notifications and multimedia keys support. - You need also to set the 'music_dir' option for mpDris2 to export covers + Use the configuration to enable notifications and multimedia keys support (on + the GNOME desktop). +diff --git a/src/Makefile.am b/src/Makefile.am +index a009560..c1b6c4a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,11 +1,12 @@ @@ -33,9 +35,9 @@ autostartdir = ${sysconfdir}/xdg/autostart/ +examplesdir = ${docdir}/examples/ - dist_bin_SCRIPTS = mpDris2 - dist_desktop_DATA = mpDris2.desktop - autostart_DATA = mpDris2.desktop + bin_SCRIPTS = mpDris2 + dist_desktop_DATA = mpdris2.desktop + autostart_DATA = mpdris2.desktop -dist_doc_DATA = mpDris2.conf +dist_examples_DATA = mpDris2.conf nodist_dbus_DATA = org.mpris.MediaPlayer2.mpd.service diff -Nru mpdris2-0.3/debian/patches/series mpdris2-0.4/debian/patches/series --- mpdris2-0.3/debian/patches/series 2012-11-16 13:15:08.000000000 +0000 +++ mpdris2-0.4/debian/patches/series 2013-04-29 08:35:06.000000000 +0000 @@ -1,3 +1,3 @@ -0001-Regenerate-edited-files-if-the-source-file-changes.patch -0002-Follow-the-XDG-Base-Directory-spec-don-t-just-assume.patch +0001-Rename-desktop-entry-to-all-lower-case.patch +0002-Set-X-GNOME-UsesNotifications-so-we-appear-in-gnome-.patch 0003-Put-example-configuration-in-u-s-d-mpdris2-examples.patch diff -Nru mpdris2-0.3/src/Makefile.am mpdris2-0.4/src/Makefile.am --- mpdris2-0.3/src/Makefile.am 2012-02-05 11:57:26.000000000 +0000 +++ mpdris2-0.4/src/Makefile.am 2012-10-28 09:26:37.000000000 +0000 @@ -2,7 +2,7 @@ dbusdir = ${datadir}/dbus-1/services/ autostartdir = ${sysconfdir}/xdg/autostart/ -dist_bin_SCRIPTS = mpDris2 +bin_SCRIPTS = mpDris2 dist_desktop_DATA = mpDris2.desktop autostart_DATA = mpDris2.desktop dist_doc_DATA = mpDris2.conf @@ -14,7 +14,7 @@ edit = sed -e 's|@bindir[@]|$(bindir)|g' \ -e 's|@datadir[@]|$(datadir)|g' -mpDris2: Makefile +mpDris2: mpDris2.in Makefile rm -f $@ $@.tmp $(MKDIR_P) $(@D) srcdir='' @@ -23,7 +23,7 @@ mv $@.tmp $@ ln -sf $@ $@.py -org.mpris.MediaPlayer2.mpd.service: Makefile +org.mpris.MediaPlayer2.mpd.service: org.mpris.MediaPlayer2.mpd.service.in Makefile rm -f $@ $@.tmp $(MKDIR_P) $(@D) srcdir='' diff -Nru mpdris2-0.3/src/Makefile.in mpdris2-0.4/src/Makefile.in --- mpdris2-0.3/src/Makefile.in 2012-10-14 08:50:47.000000000 +0000 +++ mpdris2-0.4/src/Makefile.in 2013-02-02 13:47:02.000000000 +0000 @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.11.6 from Makefile.am. +# Makefile.in generated by automake 1.11.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -17,23 +17,6 @@ VPATH = @srcdir@ -am__make_dryrun = \ - { \ - am__dry=no; \ - case $$MAKEFLAGS in \ - *\\[\ \ ]*) \ - echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ - | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ - *) \ - for am__flg in $$MAKEFLAGS; do \ - case $$am__flg in \ - *=*|--*) ;; \ - *n*) am__dry=yes; break;; \ - esac; \ - done;; \ - esac; \ - test $$am__dry = yes; \ - } pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ @@ -93,18 +76,12 @@ SCRIPTS = $(dist_bin_SCRIPTS) SOURCES = DIST_SOURCES = -am__can_run_installinfo = \ - case $$AM_UPDATE_INFO_DIR in \ - n|no|NO) false;; \ - *) (install-info --version) >/dev/null 2>&1;; \ - esac DATA = $(autostart_DATA) $(dist_desktop_DATA) $(dist_doc_DATA) \ $(nodist_dbus_DATA) DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ ALL_LINGUAS = @ALL_LINGUAS@ AMTAR = @AMTAR@ -AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ @@ -132,10 +109,6 @@ INTLTOOL_MERGE = @INTLTOOL_MERGE@ INTLTOOL_PERL = @INTLTOOL_PERL@ INTLTOOL_UPDATE = @INTLTOOL_UPDATE@ -INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@ -INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@ -INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@ -INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ @@ -187,8 +160,6 @@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ -intltool__v_merge_options_ = @intltool__v_merge_options_@ -intltool__v_merge_options_0 = @intltool__v_merge_options_0@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ @@ -260,11 +231,8 @@ $(am__aclocal_m4_deps): install-dist_binSCRIPTS: $(dist_bin_SCRIPTS) @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" @list='$(dist_bin_SCRIPTS)'; test -n "$(bindir)" || list=; \ - if test -n "$$list"; then \ - echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ - $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ - fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ @@ -295,11 +263,8 @@ dir='$(DESTDIR)$(bindir)'; $(am__uninstall_files_from_dir) install-autostartDATA: $(autostart_DATA) @$(NORMAL_INSTALL) + test -z "$(autostartdir)" || $(MKDIR_P) "$(DESTDIR)$(autostartdir)" @list='$(autostart_DATA)'; test -n "$(autostartdir)" || list=; \ - if test -n "$$list"; then \ - echo " $(MKDIR_P) '$(DESTDIR)$(autostartdir)'"; \ - $(MKDIR_P) "$(DESTDIR)$(autostartdir)" || exit 1; \ - fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ @@ -316,11 +281,8 @@ dir='$(DESTDIR)$(autostartdir)'; $(am__uninstall_files_from_dir) install-dist_desktopDATA: $(dist_desktop_DATA) @$(NORMAL_INSTALL) + test -z "$(desktopdir)" || $(MKDIR_P) "$(DESTDIR)$(desktopdir)" @list='$(dist_desktop_DATA)'; test -n "$(desktopdir)" || list=; \ - if test -n "$$list"; then \ - echo " $(MKDIR_P) '$(DESTDIR)$(desktopdir)'"; \ - $(MKDIR_P) "$(DESTDIR)$(desktopdir)" || exit 1; \ - fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ @@ -337,11 +299,8 @@ dir='$(DESTDIR)$(desktopdir)'; $(am__uninstall_files_from_dir) install-dist_docDATA: $(dist_doc_DATA) @$(NORMAL_INSTALL) + test -z "$(docdir)" || $(MKDIR_P) "$(DESTDIR)$(docdir)" @list='$(dist_doc_DATA)'; test -n "$(docdir)" || list=; \ - if test -n "$$list"; then \ - echo " $(MKDIR_P) '$(DESTDIR)$(docdir)'"; \ - $(MKDIR_P) "$(DESTDIR)$(docdir)" || exit 1; \ - fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ @@ -358,11 +317,8 @@ dir='$(DESTDIR)$(docdir)'; $(am__uninstall_files_from_dir) install-nodist_dbusDATA: $(nodist_dbus_DATA) @$(NORMAL_INSTALL) + test -z "$(dbusdir)" || $(MKDIR_P) "$(DESTDIR)$(dbusdir)" @list='$(nodist_dbus_DATA)'; test -n "$(dbusdir)" || list=; \ - if test -n "$$list"; then \ - echo " $(MKDIR_P) '$(DESTDIR)$(dbusdir)'"; \ - $(MKDIR_P) "$(DESTDIR)$(dbusdir)" || exit 1; \ - fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ diff -Nru mpdris2-0.3/src/mpDris2 mpdris2-0.4/src/mpDris2 --- mpdris2-0.3/src/mpDris2 2012-10-14 08:50:49.000000000 +0000 +++ mpdris2-0.4/src/mpDris2 2013-02-02 13:47:04.000000000 +0000 @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # # 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 @@ -13,7 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -# Authors: Erik Karlsson , Jean-Philippe Braun +# Authors: Jean-Philippe Braun , +# Mantas Mikulėnas +# Based on mpDris from: Erik Karlsson # Some bits taken from quodlibet mpris plugin by import os @@ -23,7 +26,6 @@ import signal import socket import getopt -import types import mpd import gobject import dbus @@ -32,6 +34,7 @@ import ConfigParser import logging import gettext +import time gettext.bindtextdomain('mpDris2', '/usr/local/share/locale') gettext.textdomain('mpDris2') @@ -39,11 +42,14 @@ identity = "Music Player Daemon" params = { + 'progname': sys.argv[0], + # Connection 'host': 'localhost', 'port': 6600, 'password': None, - 'progname': sys.argv[0], + # Library 'music_dir': '', + # Bling 'mmkeys': True, 'notify': True, } @@ -82,8 +88,7 @@ } # python dbus bindings don't include annotations and properties -MPRIS2_INTROSPECTION = \ -""" +MPRIS2_INTROSPECTION = """ @@ -190,63 +195,79 @@ """ - # Default url handlers if MPD doesn't support 'urlhandlers' command -urlhandlers = [ 'http://' ] -downloaded_covers = [ '.covers/%s-%s.jpg' ] -local_covers = [ 'cover.jpg', 'cover.png', 'album.jpg', 'front.jpg', - 'folder.jpg', '.folder.jpg', '.folder.png', 'AlbumArt.jpg', - 'AlbumArtSmall.jpg' ] +urlhandlers = ['http://'] +downloaded_covers = ['~/.covers/%s-%s.jpg'] +covers_names = ('cover', 'front', 'album', 'folder', '.folder', 'Album',) +covers_exts = ('jpg', 'png', 'jpeg', 'gif',) + -# Wrapper to handle socket errors and similar class MPDWrapper(mpd.MPDClient): + """ Wrapper of mpd.MPDClient to handle socket + errors and similar + """ def __init__(self, params): mpd.MPDClient.__init__(self) self._dbus = dbus self._params = params - self._monitor = False - self._status = False - self._position = 0 self._dbus_service = False + + self._can_single = False + self._can_idle = False + self._errors = 0 - # init mmkeys, if configured + self._poll_id = None + self._watch_id = None + self._idling = False + + self._status = False + self._position = 0 + self._time = 0 + + self._bus = dbus.SessionBus() if self._params['mmkeys']: - try: - gsd_object = dbus.SessionBus().get_object('org.gnome.SettingsDaemon', - '/org/gnome/SettingsDaemon/MediaKeys') - # this is what gives us the multi media keys. - gsd_object.GrabMediaPlayerKeys('mpDris2', 0, - dbus_interface='org.gnome.SettingsDaemon.MediaKeys') - # connect_to_signal registers our callback function. - gsd_object.connect_to_signal('MediaPlayerKeyPressed', self.on_mediakey) - except: - logger.error('Failed to connect to GNOME Settings Daemon.' \ - ' Disabling multimedia key support') - params['mmkeys'] = False + self.setup_mediakeys() def run(self): - """ Try to connect to MPD """ + """ + Try to connect to MPD; retry every 5 seconds on failure. + """ if self.my_connect(): - # If connection fail retry in 5 seconds - gobject.timeout_add(5000, self.my_connect) + gobject.timeout_add_seconds(5, self.my_connect) + return False + else: + return True def my_connect(self): """ Init MPD connection """ try: - # Connect to host + self._idling = False + self._can_idle = False + self._can_single = False + self.connect(self._params['host'], self._params['port']) if params['password']: self.password(self._params['password']) - # Get URL handlers supported by MPD - global urlhandlers - if 'urlhandlers' in self.commands(): + + commands = self.commands() + # added in 0.11 + if 'urlhandlers' in commands: + global urlhandlers urlhandlers = self.urlhandlers() + # added in 0.14 + if 'idle' in commands: + self._can_idle = True + # added in 0.15 + if 'single' in commands: + self._can_single = True + if self._errors > 0: notification.rnotify(identity, _('Reconnected')) - logger.debug('Reconnected to MPD server.') + logger.info('Reconnected to MPD server.') else: logger.debug('Connected to MPD server.') + # Make the socket non blocking to detect deconnections self._sock.settimeout(5.0) # Export our DBUS service @@ -254,15 +275,27 @@ self._dbus_service = MPRISInterface(self._params['music_dir']) else: # Add our service to the session bus - self._dbus_service.add_to_connection(dbus.SessionBus(), - '/org/mpris/MediaPlayer2') - self._dbus_service.aquire_name() + #self._dbus_service.add_to_connection(dbus.SessionBus(), + # '/org/mpris/MediaPlayer2') + self._dbus_service.acquire_name() + # Init internal state to throw events at start self.init_state() + # Add periodic status check for sending MPRIS events - self._monitor = gobject.timeout_add(1000, self.monitor) + if not self._poll_id: + interval = 15 if self._can_idle else 1 + self._poll_id = gobject.timeout_add_seconds(interval, + self.timer_callback) + if self._can_idle and not self._watch_id: + self._watch_id = gobject.io_add_watch(self, + gobject.IO_IN | gobject.IO_HUP, + self.socket_callback) # Reset error counter self._errors = 0 + + self.timer_callback() + self.idle_enter() # Return False to stop trying to connect return False except socket.error as e: @@ -279,14 +312,25 @@ def reconnect(self): logger.warning("Disconnected") notification.rnotify(identity, _('Disconnected'), 'error') - # Release the Dbus name - self._dbus_service.release_name(); - # Disconnect service from the session bus - self._dbus_service.remove_from_connection(); + + # Release the DBus name and disconnect from bus + self._dbus_service.release_name() + #self._dbus_service.remove_from_connection() + # Stop monitoring - gobject.source_remove(self._monitor) + if self._poll_id: + gobject.source_remove(self._poll_id) + self._poll_id = None + if self._watch_id: + gobject.source_remove(self._watch_id) + self._watch_id = None + # Clean mpd client state - self.disconnect() + try: + self.disconnect() + except: + self.disconnect() + # Try to reconnect self.run() @@ -298,57 +342,67 @@ self._status['songid'] = '-1' self._position = 0 - def monitor(self): - old_status = self._status - old_position = self._position - - self._status = self.status() - - if type(self._status) == types.DictType: - - if 'time' in self._status: - self._position = int(self._status['time'].split(':')[0]) - else: - self._position = 0 + def idle_enter(self): + if not self._can_idle: + return False + if not self._idling: + # NOTE: do not use MPDClient.idle(), which waits for an event + self._write_command("idle", []) + self._idling = True + logger.debug("Entered idle") + return True + else: + logger.warning("Nested idle_enter()!") + return False - if self._status['state'] != 'stop': - if not 'songid' in old_status or old_status['songid'] != self._status['songid']: - metadata = self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Metadata') - if self._params['notify']: - uri = 'sound' - if 'mpris:artUrl' in metadata: - uri = metadata['mpris:artUrl'] - title = 'Unknown Title' - if 'xesam:title' in metadata: - title = metadata['xesam:title'] - artist = 'Unknown Artist' - if 'xesam:artist' in metadata: - artist = metadata['xesam:artist'][0] - notification.notify(title, _('by %s') % artist, uri) - - if (abs(self._position - old_position) > 2 and \ - old_status['songid'] == self._status['songid']) or \ - not 'songid' in old_status or \ - old_status['songid'] == '-1': - self._dbus_service.Seeked(self._position * 1000000) - - - if old_status['volume'] != self._status['volume']: - self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Volume') - - if old_status['state'] != self._status['state']: - self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'PlaybackStatus') + def idle_leave(self): + if not self._can_idle: + return False + if self._idling: + # NOTE: don't use noidle() or _execute() to avoid infinite recursion + self._write_command("noidle", []) + self._fetch_object() + self._idling = False + logger.debug("Left idle") + return True + else: + return False - if old_status['random'] != self._status['random']: - self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Shuffle') + ## Events - if old_status['repeat'] != self._status['repeat']: - self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'LoopStatus') + def timer_callback(self): + try: + was_idle = self.idle_leave() + except (socket.error, mpd.MPDError, socket.timeout): + self.reconnect() + return False + self._update_properties() + if was_idle: + self.idle_enter() + return True - # Return True to continue polling + def socket_callback(self, fd, event): + logger.debug("Socket event %r on fd %r" % (event, fd)) + if event & gobject.IO_HUP: + self.reconnect() + return True + elif event & gobject.IO_IN: + if self._idling: + self._idling = False + data = fd._fetch_objects("changed") + logger.debug("Idle events: %r" % data) + updated = False + for item in data: + subsystem = item["changed"] + # subsystem list: + if subsystem in ("player", "mixer", "options", "playlist"): + if not updated: + self._update_properties() + updated = True + self.idle_enter() return True - def on_mediakey(self, appname, key): + def mediakey_callback(self, appname, key): """ GNOME media key handler """ logger.debug('Got GNOME mmkey "%s" for "%s"' % (key, appname)) if key == 'Play': @@ -366,13 +420,171 @@ notification.rnotify(identity, _('Stopped')) self.stop() - # Catch connection errors when calling mpd client methods - def _execute(self, command, args): + def last_currentsong(self): + return self._currentsong.copy() + + def last_status(self): + if time.time() - self._time >= 2: + self.timer_callback() + return self._status.copy() + + def _update_properties(self): + old_status = self._status + old_position = self._position + old_time = self._time + self._currentsong = self.currentsong() + self._status = new_status = self.status() + self._time = new_time = int(time.time()) + + if not new_status: + return + + if 'elapsed' in new_status: + new_position = float(new_status['elapsed']) + elif 'time' in new_status: + new_position = int(new_status['time'].split(':')[0]) + else: + new_position = 0 + + self._position = new_position + + # "player" subsystem + + if old_status['state'] != new_status['state']: + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'PlaybackStatus') + + if old_status.get('songid', None) == new_status.get('songid', None): + if new_status['state'] == 'play': + expected_position = old_position + (new_time - old_time) + else: + expected_position = old_position + if abs(new_position - expected_position) > 0.6: + logger.debug("Expected pos %r, actual %r, diff %r" % ( + expected_position, new_position, new_position - expected_position)) + logger.debug("Old position was %r at %r (%r seconds ago)" % ( + old_position, old_time, new_time - old_time)) + self._dbus_service.Seeked(new_position * 1000000) + + else: + metadata = self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'Metadata') + if self._params['notify']: + uri = 'sound' + if 'mpris:artUrl' in metadata: + uri = metadata['mpris:artUrl'] + title = 'Unknown Title' + if 'xesam:title' in metadata: + title = metadata['xesam:title'] + artist = 'Unknown Artist' + if 'xesam:artist' in metadata: + artist = ", ".join(metadata['xesam:artist']) + notification.notify(title, _('by %s') % artist, uri) + + # "mixer" subsystem + + if old_status['volume'] != new_status['volume']: + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'Volume') + + # "options" subsystem + # also triggered if consume, crossfade or ReplayGain are updated + + if old_status['random'] != new_status['random']: + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'Shuffle') + + if (old_status['repeat'] != new_status['repeat'] + or old_status.get('single', 0) != new_status.get('single', 0)): + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'LoopStatus') + + if ("nextsongid" in old_status) != ("nextsongid" in new_status): + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'CanGoNext') + + ## Media keys + + def setup_mediakeys(self): + self.register_mediakeys() + self._dbus_obj = self._bus.get_object("org.freedesktop.DBus", + "/org/freedesktop/DBus") + self._dbus_obj.connect_to_signal("NameOwnerChanged", + self.gsd_name_owner_changed_callback, + arg0="org.gnome.SettingsDaemon") + + def register_mediakeys(self): try: - return mpd.MPDClient._execute(self, command, args) - except (socket.error, mpd.MPDError, socket.timeout): - self.reconnect() - return False + gsd_object = self._bus.get_object("org.gnome.SettingsDaemon", + "/org/gnome/SettingsDaemon/MediaKeys") + gsd_object.GrabMediaPlayerKeys("mpDris2", 0, + dbus_interface="org.gnome.SettingsDaemon.MediaKeys") + except: + logger.warning("Failed to connect to GNOME Settings Daemon. Media keys won't work.") + else: + self._bus.remove_signal_receiver(self.mediakey_callback) + gsd_object.connect_to_signal("MediaPlayerKeyPressed", self.mediakey_callback) + + def gsd_name_owner_changed_callback(self, bus_name, old_owner, new_owner): + if bus_name == "org.gnome.SettingsDaemon" and new_owner != "": + def reregister(): + logger.debug("Re-registering with GNOME Settings Daemon (owner %s)" % new_owner) + self.register_mediakeys() + return False + # Timeout is necessary since g-s-d takes some time to load all plugins. + gobject.timeout_add(600, reregister) + + ## Compatibility functions + + # Fedora 17 still has python-mpd 0.2, which lacks fileno(). + if not hasattr(mpd.MPDClient, "fileno"): + def fileno(self): + if self._sock is None: + raise mpd.ConnectionError("Not connected") + return self._sock.fileno() + + if not hasattr(mpd.MPDClient, "_write_command") \ + and hasattr(mpd.MPDClient, "_writecommand"): + def _write_command(self, *args): + return self._writecommand(*args) + + if not hasattr(mpd.MPDClient, "_fetch_object") \ + and hasattr(mpd.MPDClient, "_getobject"): + def _fetch_object(self, *args): + return self._getobject(*args) + + if not hasattr(mpd.MPDClient, "_fetch_objects") \ + and hasattr(mpd.MPDClient, "_getobjects"): + def _fetch_objects(self, *args): + return self._getobjects(*args) + + # Catch connection errors when calling mpd client methods + if hasattr(mpd.MPDClient, "_execute"): + def _execute(self, command, args): + try: + was_idle = self.idle_leave() + logger.debug("Sending command %r (during idle? %r)" % (command, was_idle)) + r = mpd.MPDClient._execute(self, command, args) + if was_idle: + self.idle_enter() + return r + except (socket.error, mpd.MPDError, socket.timeout): + self.reconnect() + return False + elif hasattr(mpd.MPDClient, "_docommand"): + # Again for python-mpd 0.2 + def _docommand(self, command, args, retval): + try: + was_idle = self.idle_leave() + logger.debug("Sending command %r (during idle? %r)" % (command, was_idle)) + r = mpd.MPDClient._docommand(self, command, args, retval) + if was_idle: + self.idle_enter() + return r + except (socket.error, mpd.MPDError, socket.timeout): + self.reconnect() + return False + class Notify: def __init__(self, params): @@ -381,19 +593,19 @@ if pynotify.init(identity): self._notification = pynotify.Notification("", "", "") else: - logger.error('Failed to init libnotify; disabling' \ - 'notifications') + logger.error('Failed to init libnotify; disabling' + 'notifications') self._notification = False else: self._notification = False - def notify(self, title, body, uri = ''): + def notify(self, title, body, uri=''): """ Issue a new notification """ if self._notification: self._notification = pynotify.Notification(title, body, uri) self._notification.show() - def rnotify(self, title, body, uri = ''): + def rnotify(self, title, body, uri=''): """ Replace current notification """ if self._notification: self._notification.update(title, body, uri) @@ -408,18 +620,39 @@ __introspect_interface = "org.freedesktop.DBus.Introspectable" __prop_interface = dbus.PROPERTIES_IFACE - def __init__(self, bus, path = ""): + def __init__(self, bus, path=""): dbus.service.Object.__init__(self, dbus.SessionBus(), - MPRISInterface.__path) + MPRISInterface.__path) self.path = path - self.aquire_name() - def aquire_name(self): + self._bus = dbus.SessionBus() + self._uname = self._bus.get_unique_name() + self._dbus_obj = self._bus.get_object("org.freedesktop.DBus", + "/org/freedesktop/DBus") + self._dbus_obj.connect_to_signal("NameOwnerChanged", + self._name_owner_changed_callback, + arg0=self.__name) + + self.acquire_name() + + def _name_owner_changed_callback(self, name, old_owner, new_owner): + if name == self.__name and old_owner == self._uname and new_owner != "": + try: + pid = self._dbus_obj.GetConnectionUnixProcessID(new_owner) + except: + pid = None + logger.info("Replaced by %s (PID %s)" % (new_owner, pid or "unknown")) + loop.quit() + + def acquire_name(self): self._bus_name = dbus.service.BusName(MPRISInterface.__name, - bus=dbus.SessionBus()) + bus=self._bus, + allow_replacement=True, + replace_existing=True) def release_name(self): - del self._bus_name + if hasattr(self, "_bus_name"): + del self._bus_name __root_interface = "org.mpris.MediaPlayer2" __root_props = { @@ -433,37 +666,52 @@ } def __get_playback_status(): - status = mpd_wrapper.status() + status = mpd_wrapper.last_status() return {'play': 'Playing', 'pause': 'Paused', 'stop': 'Stopped'}[status['state']] def __set_loop_status(value): - if str(value) == "Playlist": + if value == "Playlist": mpd_wrapper.repeat(1) - elif str(value) == "None": + if mpd_wrapper._can_single: + mpd_wrapper.single(0) + elif value == "Track": + if mpd_wrapper._can_single: + mpd_wrapper.repeat(1) + mpd_wrapper.single(1) + elif value == "None": mpd_wrapper.repeat(0) + if mpd_wrapper._can_single: + mpd_wrapper.single(0) else: - raise dbus.exceptions.DBusException("Loop mode not supported") + raise dbus.exceptions.DBusException("Loop mode %r not supported" % + value) return def __get_loop_status(): - status = mpd_wrapper.status() - return ("None", "Playlist")[int(status['repeat'])] + status = mpd_wrapper.last_status() + if int(status['repeat']) == 1: + if int(status.get('single', 0)) == 1: + return "Track" + else: + return "Playlist" + else: + return "None" def __set_shuffle(value): mpd_wrapper.random(value) return def __get_shuffle(): - if mpd_wrapper.status()['random'] == '1': + if int(mpd_wrapper.last_status()['random']) == 1: return True else: return False def __get_metadata(): - return format_metadata(mpd_wrapper.currentsong()) + return format_metadata(mpd_wrapper.last_currentsong()) def __get_volume(): - vol = float(mpd_wrapper.status()['volume']) + vol = float(mpd_wrapper.last_status()['volume']) if vol > 0: return vol / 100.0 else: @@ -475,7 +723,7 @@ return def __get_position(): - status = mpd_wrapper.status() + status = mpd_wrapper.last_status() if 'time' in status: current, end = status['time'].split(':') return dbus.Int64((int(current) * 1000000)) @@ -483,7 +731,7 @@ return dbus.Int64(0) def __can_next(): - if 'nextsongid' in mpd_wrapper.status(): + if 'nextsongid' in mpd_wrapper.last_status(): return True else: return False @@ -511,7 +759,8 @@ __prop_mapping = { __player_interface: __player_props, - __root_interface: __root_props} + __root_interface: __root_props, + } @dbus.service.method(__introspect_interface) def Introspect(self): @@ -519,7 +768,7 @@ @dbus.service.signal(__prop_interface, signature="sa{sv}as") def PropertiesChanged(self, interface, changed_properties, - invalidated_properties): + invalidated_properties): pass @dbus.service.method(__prop_interface, @@ -543,7 +792,8 @@ read_props = {} props = self.__prop_mapping[interface] for key, (getter, setter) in props.iteritems(): - if callable(getter): getter = getter() + if callable(getter): + getter = getter() read_props[key] = getter return read_props @@ -615,16 +865,18 @@ offset = int(offset) / 1000000 if current + offset <= end: position = current + offset - if position < 0: position = 0 + if position < 0: + position = 0 mpd_wrapper.seekid(int(status['songid']), position) self.Seeked(position * 1000000) return @dbus.service.method(__player_interface, in_signature='ox', out_signature='') def SetPosition(self, trackid, position): - song = mpd_wrapper.currentsong() + song = mpd_wrapper.last_currentsong() # FIXME: use real dbus objects - if str(trackid) != '/org/mpris/MediaPlayer2/Track/%s' % song['id']: return + if str(trackid) != '/org/mpris/MediaPlayer2/Track/%s' % song['id']: + return # Convert position to seconds position = int(position) / 1000000 if position <= int(song['time']): @@ -642,90 +894,110 @@ # TODO return + # Handle signals more gracefully def handle_sigint(signum, frame): logger.debug('Caught SIGINT, exiting.') loop.quit() -def format_metadata(metadata): - """http://xmms2.org/wiki/MPRIS_Metadata""" - if 'id' in metadata: - metadata['mpris:trackid'] = "/org/mpris/MediaPlayer2/Track/%s" % metadata['id'] +def find_cover(song_url, metadata): + if song_url.startswith('file://'): + # Look in song directory for common album cover files + song_dir = os.path.dirname(song_url[7:]) + if os.path.exists(song_dir): + for f in os.listdir(song_dir): + if f.startswith(covers_names) and f.endswith(covers_exts): + return 'file://' + os.path.join(song_dir, f) + + # Search the shared cover directories + if 'xesam:artist' in metadata and 'xesam:album' in metadata: + artist = ",".join(metadata['xesam:artist']) + album = metadata['xesam:album'] + for template in downloaded_covers: + f = os.path.expanduser(template % (artist, album)) + if os.path.exists(f): + return 'file://' + f - if 'time' in metadata: - metadata['mpris:length'] = int(metadata['time']) * 1000000 + return None - if 'date' in metadata: - if len(metadata['date']) is 4: - metadata['xesam:contentCreated'] = metadata['date'] - else: - metadata['xesam:contentCreated'] = metadata['date'][0:4] - if 'track' in metadata: - if type(metadata['track']) == list and len(metadata['track']) > 0: - track = str(metadata['track'][0]) +def format_metadata(mpd_meta): + """ + Translate metadata returned by MPD to the MPRIS v2 syntax. + http://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata + """ + + metadata = {} + + for tag in ('album', 'title'): + if tag in mpd_meta: + metadata['xesam:%s' % tag] = mpd_meta[tag] + + if 'id' in mpd_meta: + metadata['mpris:trackid'] = "/org/mpris/MediaPlayer2/Track/%s" % \ + mpd_meta['id'] + + if 'time' in mpd_meta: + metadata['mpris:length'] = int(mpd_meta['time']) * 1000000 + + if 'date' in mpd_meta: + metadata['xesam:contentCreated'] = mpd_meta['date'][0:4] + + if 'track' in mpd_meta: + # TODO: Is it even *possible* for mpd_meta['track'] to be a list? + if type(mpd_meta['track']) == list and len(mpd_meta['track']) > 0: + track = str(mpd_meta['track'][0]) else: - track = str(metadata['track']) - if re.match('^([0-9]+).*', track): - metadata['xesam:trackNumber'] = int(re.match('^([0-9]+).*', track).group(1)) + track = str(mpd_meta['track']) + + m = re.match('^([0-9]+)', track) + if m: + metadata['xesam:trackNumber'] = int(m.group(1)) # Ensure the integer is signed 32bit if metadata['xesam:trackNumber'] & 0x80000000: metadata['xesam:trackNumber'] += -0x100000000 else: metadata['xesam:trackNumber'] = 0 - if 'disc' in metadata: - if type(metadata['disc']) == list and len(metadata['disc']) > 0: - disc = str(metadata['disc'][0]) - else: - disc = str(metadata['disc']) - if re.match('^([0-9]+).*', disc): - metadata['xesam:discNumber'] = int(re.match('^([0-9]+).*', disc).group(1)) - - if 'artist' in metadata: - metadata['xesam:artist'] = [metadata['artist'],] - - if 'composer' in metadata: - metadata['xesam:composer'] = [metadata['composer'],] - - mpd_tags = ('album', 'title') - for tag in mpd_tags: - if tag in metadata: - metadata['xesam:%s' % tag] = metadata[tag] - - if 'file' in metadata: - file = metadata['file'] - if len([ x for x in urlhandlers if file.startswith(x) ]) == 0: - file = os.path.join(params['music_dir'], file) - metadata['xesam:url'] = file - if file.startswith('file://'): - basedirpath = os.path.dirname(file).replace('file://', '') - cover = False - if os.path.exists(basedirpath): - for f in os.listdir(basedirpath): - if f in local_covers: - cover = 'file://' + os.path.join(basedirpath, f) - if not cover and 'artist' in metadata and 'album' in metadata: - for f in downloaded_covers: - f = os.path.expanduser(os.path.join('~', f % \ - (metadata['artist'], metadata['album']))) - if os.path.exists(f): - cover = 'file://' + f - if cover: - metadata['mpris:artUrl'] = cover + if 'disc' in mpd_meta: + # TODO: Same as above. When is it a list? + if type(mpd_meta['disc']) == list and len(mpd_meta['disc']) > 0: + disc = str(mpd_meta['disc'][0]) + else: + disc = str(mpd_meta['disc']) + + m = re.match('^([0-9]+)', disc) + if m: + metadata['xesam:discNumber'] = int(m.group(1)) + + if 'artist' in mpd_meta: + if type(mpd_meta['artist']) == list: + metadata['xesam:artist'] = mpd_meta['artist'] + else: + metadata['xesam:artist'] = [mpd_meta['artist']] + + if 'composer' in mpd_meta: + if type(mpd_meta['composer']) == list: + metadata['xesam:composer'] = mpd_meta['composer'] + else: + metadata['xesam:composer'] = [mpd_meta['composer']] + + if 'file' in mpd_meta: + song_url = mpd_meta['file'] + if not any([song_url.startswith(prefix) for prefix in urlhandlers]): + song_url = os.path.join(params['music_dir'], song_url) + metadata['xesam:url'] = song_url + cover = find_cover(song_url, metadata) + if cover: + metadata['mpris:artUrl'] = cover # Stream: populate some missings tags with stream's name - if 'name' in metadata: + if 'name' in mpd_meta: if 'xesam:title' not in metadata: - metadata['xesam:title'] = metadata['name'] + metadata['xesam:title'] = mpd_meta['name'] elif 'xesam:album' not in metadata: - metadata['xesam:album'] = metadata['name'] - - surplus_tags = set(metadata.keys()).difference(set(allowed_tags.keys())) - # Remove surplus tags - for tag in surplus_tags: - del metadata[tag] + metadata['xesam:album'] = mpd_meta['name'] # Cast metadata to the correct type, or discard it for key, value in metadata.items(): @@ -733,30 +1005,58 @@ metadata[key] = allowed_tags[key](value) except ValueError: del metadata[key] - logger.error("Can't cast value %s to %s" % \ - (value, allowed_tags[key])) + logger.error("Can't cast value %r to %s" % + (value, allowed_tags[key])) return dbus.Dictionary(metadata, signature='sv') + +def each_xdg_config(suffix): + """ + Return each location matching XDG_CONFIG_DIRS/suffix in descending + priority order. + """ + config_home = os.environ.get('XDG_CONFIG_HOME', + os.path.expanduser('~/.config')) + config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':') + return ([os.path.join(config_home, suffix)] + + [os.path.join(d, suffix) for d in config_dirs]) + + +def open_first_xdg_config(suffix): + """ + Try to open each location matching XDG_CONFIG_DIRS/suffix as a file. + Return the first that can be opened successfully, or None. + """ + for filename in each_xdg_config(suffix): + try: + f = open(filename, 'r') + except IOError: + pass + else: + return f + else: + return None + + def find_music_dir(): if 'XDG_MUSIC_DIR' in os.environ: return os.environ['XDG_MUSIC_DIR'] - conf = os.path.expanduser('~/.config/user-dirs.dirs') - try: - for line in open(conf, 'r'): + conf = open_first_xdg_config('user-dirs.dirs') + if conf is not None: + for line in conf: if not line.startswith('XDG_MUSIC_DIR='): continue # use shlex to handle "shell escaping" - path = shlex.split(line)[0][14:] + path = shlex.split(line[14:])[0] if path.startswith('$HOME/'): return os.path.expanduser('~' + path[5:]) elif path.startswith('/'): return path else: - continue # other forms are not supported - except IOError: - pass + # other forms are not supported + break paths = '~/Music', '~/music' for path in map(os.path.expanduser, paths): @@ -765,6 +1065,7 @@ return None + def usage(params): print """\ Usage: %(progname)s [OPTION]... [MPD_HOST] [MPD_PORT] @@ -782,10 +1083,11 @@ if __name__ == '__main__': DBusGMainLoop(set_as_default=True) - path = find_music_dir() + music_dir = find_music_dir() config = ConfigParser.SafeConfigParser() - config.read(['/etc/mpDris2.conf', os.path.expanduser('~/.config/mpDris2/mpDris2.conf')]) + config.read(['/etc/mpDris2.conf'] + + list(reversed(each_xdg_config('mpDris2/mpDris2.conf')))) if config.has_option('Connection', 'host'): params['host'] = config.get('Connection', 'host') @@ -794,13 +1096,17 @@ if config.has_option('Connection', 'password'): params['password'] = config.get('Connection', 'password') if config.has_option('Connection', 'music_dir'): - path = config.get('Connection', 'music_dir') + music_dir = config.get('Connection', 'music_dir') if 'MPD_HOST' in os.environ: params['host'] = os.environ['MPD_HOST'] if 'MPD_PORT' in os.environ: params['port'] = os.environ['MPD_PORT'] + for bling in ['mmkeys', 'notify']: + if config.has_option('Bling', bling): + params[bling] = config.getboolean('Bling', bling) + try: (opts, args) = getopt.getopt(sys.argv[1:], 'hdp:', ['help', 'debug', 'path=']) except getopt.GetoptError, (msg, opt): @@ -817,7 +1123,7 @@ usage(params) sys.exit() elif opt in ['-p', '--path']: - path = arg + music_dir = arg elif opt in ['-d', '--debug']: log_level = logging.DEBUG @@ -837,17 +1143,19 @@ if '@' in params['host']: params['password'], params['host'] = params['host'].split('@', 1) - if path and os.path.exists(path): - logger.info('Using %s as music library path' % path) - if not path.startswith('file://'): - params['music_dir'] = 'file://' + path + if music_dir: + # Ensure that music_dir starts with an URL scheme. + if not re.match('^[0-9A-Za-z+.-]+://', music_dir): + music_dir = 'file://' + music_dir + if music_dir.startswith('file://'): + if not os.path.exists(music_dir[7:]): + logger.error('Music library path %s does not exist!' % music_dir) + # Non-local URLs can still be useful to MPRIS clients, so accept them. + params['music_dir'] = music_dir + logger.info('Using %s as music library path.' % music_dir) else: - logger.warning('By not supplying a path for the music library ' \ - 'this program will break the MPRIS specification!') - - for bling in ['mmkeys','notify']: - if config.has_option('Bling', bling): - params[bling] = config.getboolean('Bling', bling) + logger.warning('By not supplying a path for the music library ' + 'this program will break the MPRIS specification!') loop = gobject.MainLoop() signal.signal(signal.SIGINT, handle_sigint) diff -Nru mpdris2-0.3/src/mpDris2.in mpdris2-0.4/src/mpDris2.in --- mpdris2-0.3/src/mpDris2.in 2012-10-14 08:37:35.000000000 +0000 +++ mpdris2-0.4/src/mpDris2.in 2013-01-13 11:09:34.000000000 +0000 @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # # 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 @@ -13,7 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -# Authors: Erik Karlsson , Jean-Philippe Braun +# Authors: Jean-Philippe Braun , +# Mantas Mikulėnas +# Based on mpDris from: Erik Karlsson # Some bits taken from quodlibet mpris plugin by import os @@ -23,7 +26,6 @@ import signal import socket import getopt -import types import mpd import gobject import dbus @@ -32,6 +34,7 @@ import ConfigParser import logging import gettext +import time gettext.bindtextdomain('mpDris2', '@datadir@/locale') gettext.textdomain('mpDris2') @@ -39,11 +42,14 @@ identity = "Music Player Daemon" params = { + 'progname': sys.argv[0], + # Connection 'host': 'localhost', 'port': 6600, 'password': None, - 'progname': sys.argv[0], + # Library 'music_dir': '', + # Bling 'mmkeys': True, 'notify': True, } @@ -82,8 +88,7 @@ } # python dbus bindings don't include annotations and properties -MPRIS2_INTROSPECTION = \ -""" +MPRIS2_INTROSPECTION = """ @@ -190,63 +195,79 @@ """ - # Default url handlers if MPD doesn't support 'urlhandlers' command -urlhandlers = [ 'http://' ] -downloaded_covers = [ '.covers/%s-%s.jpg' ] -local_covers = [ 'cover.jpg', 'cover.png', 'album.jpg', 'front.jpg', - 'folder.jpg', '.folder.jpg', '.folder.png', 'AlbumArt.jpg', - 'AlbumArtSmall.jpg' ] +urlhandlers = ['http://'] +downloaded_covers = ['~/.covers/%s-%s.jpg'] +covers_names = ('cover', 'front', 'album', 'folder', '.folder', 'Album',) +covers_exts = ('jpg', 'png', 'jpeg', 'gif',) + -# Wrapper to handle socket errors and similar class MPDWrapper(mpd.MPDClient): + """ Wrapper of mpd.MPDClient to handle socket + errors and similar + """ def __init__(self, params): mpd.MPDClient.__init__(self) self._dbus = dbus self._params = params - self._monitor = False - self._status = False - self._position = 0 self._dbus_service = False + + self._can_single = False + self._can_idle = False + self._errors = 0 - # init mmkeys, if configured + self._poll_id = None + self._watch_id = None + self._idling = False + + self._status = False + self._position = 0 + self._time = 0 + + self._bus = dbus.SessionBus() if self._params['mmkeys']: - try: - gsd_object = dbus.SessionBus().get_object('org.gnome.SettingsDaemon', - '/org/gnome/SettingsDaemon/MediaKeys') - # this is what gives us the multi media keys. - gsd_object.GrabMediaPlayerKeys('mpDris2', 0, - dbus_interface='org.gnome.SettingsDaemon.MediaKeys') - # connect_to_signal registers our callback function. - gsd_object.connect_to_signal('MediaPlayerKeyPressed', self.on_mediakey) - except: - logger.error('Failed to connect to GNOME Settings Daemon.' \ - ' Disabling multimedia key support') - params['mmkeys'] = False + self.setup_mediakeys() def run(self): - """ Try to connect to MPD """ + """ + Try to connect to MPD; retry every 5 seconds on failure. + """ if self.my_connect(): - # If connection fail retry in 5 seconds - gobject.timeout_add(5000, self.my_connect) + gobject.timeout_add_seconds(5, self.my_connect) + return False + else: + return True def my_connect(self): """ Init MPD connection """ try: - # Connect to host + self._idling = False + self._can_idle = False + self._can_single = False + self.connect(self._params['host'], self._params['port']) if params['password']: self.password(self._params['password']) - # Get URL handlers supported by MPD - global urlhandlers - if 'urlhandlers' in self.commands(): + + commands = self.commands() + # added in 0.11 + if 'urlhandlers' in commands: + global urlhandlers urlhandlers = self.urlhandlers() + # added in 0.14 + if 'idle' in commands: + self._can_idle = True + # added in 0.15 + if 'single' in commands: + self._can_single = True + if self._errors > 0: notification.rnotify(identity, _('Reconnected')) - logger.debug('Reconnected to MPD server.') + logger.info('Reconnected to MPD server.') else: logger.debug('Connected to MPD server.') + # Make the socket non blocking to detect deconnections self._sock.settimeout(5.0) # Export our DBUS service @@ -254,15 +275,27 @@ self._dbus_service = MPRISInterface(self._params['music_dir']) else: # Add our service to the session bus - self._dbus_service.add_to_connection(dbus.SessionBus(), - '/org/mpris/MediaPlayer2') - self._dbus_service.aquire_name() + #self._dbus_service.add_to_connection(dbus.SessionBus(), + # '/org/mpris/MediaPlayer2') + self._dbus_service.acquire_name() + # Init internal state to throw events at start self.init_state() + # Add periodic status check for sending MPRIS events - self._monitor = gobject.timeout_add(1000, self.monitor) + if not self._poll_id: + interval = 15 if self._can_idle else 1 + self._poll_id = gobject.timeout_add_seconds(interval, + self.timer_callback) + if self._can_idle and not self._watch_id: + self._watch_id = gobject.io_add_watch(self, + gobject.IO_IN | gobject.IO_HUP, + self.socket_callback) # Reset error counter self._errors = 0 + + self.timer_callback() + self.idle_enter() # Return False to stop trying to connect return False except socket.error as e: @@ -279,14 +312,25 @@ def reconnect(self): logger.warning("Disconnected") notification.rnotify(identity, _('Disconnected'), 'error') - # Release the Dbus name - self._dbus_service.release_name(); - # Disconnect service from the session bus - self._dbus_service.remove_from_connection(); + + # Release the DBus name and disconnect from bus + self._dbus_service.release_name() + #self._dbus_service.remove_from_connection() + # Stop monitoring - gobject.source_remove(self._monitor) + if self._poll_id: + gobject.source_remove(self._poll_id) + self._poll_id = None + if self._watch_id: + gobject.source_remove(self._watch_id) + self._watch_id = None + # Clean mpd client state - self.disconnect() + try: + self.disconnect() + except: + self.disconnect() + # Try to reconnect self.run() @@ -298,57 +342,67 @@ self._status['songid'] = '-1' self._position = 0 - def monitor(self): - old_status = self._status - old_position = self._position - - self._status = self.status() - - if type(self._status) == types.DictType: - - if 'time' in self._status: - self._position = int(self._status['time'].split(':')[0]) - else: - self._position = 0 + def idle_enter(self): + if not self._can_idle: + return False + if not self._idling: + # NOTE: do not use MPDClient.idle(), which waits for an event + self._write_command("idle", []) + self._idling = True + logger.debug("Entered idle") + return True + else: + logger.warning("Nested idle_enter()!") + return False - if self._status['state'] != 'stop': - if not 'songid' in old_status or old_status['songid'] != self._status['songid']: - metadata = self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Metadata') - if self._params['notify']: - uri = 'sound' - if 'mpris:artUrl' in metadata: - uri = metadata['mpris:artUrl'] - title = 'Unknown Title' - if 'xesam:title' in metadata: - title = metadata['xesam:title'] - artist = 'Unknown Artist' - if 'xesam:artist' in metadata: - artist = metadata['xesam:artist'][0] - notification.notify(title, _('by %s') % artist, uri) - - if (abs(self._position - old_position) > 2 and \ - old_status['songid'] == self._status['songid']) or \ - not 'songid' in old_status or \ - old_status['songid'] == '-1': - self._dbus_service.Seeked(self._position * 1000000) - - - if old_status['volume'] != self._status['volume']: - self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Volume') - - if old_status['state'] != self._status['state']: - self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'PlaybackStatus') + def idle_leave(self): + if not self._can_idle: + return False + if self._idling: + # NOTE: don't use noidle() or _execute() to avoid infinite recursion + self._write_command("noidle", []) + self._fetch_object() + self._idling = False + logger.debug("Left idle") + return True + else: + return False - if old_status['random'] != self._status['random']: - self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'Shuffle') + ## Events - if old_status['repeat'] != self._status['repeat']: - self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', 'LoopStatus') + def timer_callback(self): + try: + was_idle = self.idle_leave() + except (socket.error, mpd.MPDError, socket.timeout): + self.reconnect() + return False + self._update_properties() + if was_idle: + self.idle_enter() + return True - # Return True to continue polling + def socket_callback(self, fd, event): + logger.debug("Socket event %r on fd %r" % (event, fd)) + if event & gobject.IO_HUP: + self.reconnect() + return True + elif event & gobject.IO_IN: + if self._idling: + self._idling = False + data = fd._fetch_objects("changed") + logger.debug("Idle events: %r" % data) + updated = False + for item in data: + subsystem = item["changed"] + # subsystem list: + if subsystem in ("player", "mixer", "options", "playlist"): + if not updated: + self._update_properties() + updated = True + self.idle_enter() return True - def on_mediakey(self, appname, key): + def mediakey_callback(self, appname, key): """ GNOME media key handler """ logger.debug('Got GNOME mmkey "%s" for "%s"' % (key, appname)) if key == 'Play': @@ -366,13 +420,171 @@ notification.rnotify(identity, _('Stopped')) self.stop() - # Catch connection errors when calling mpd client methods - def _execute(self, command, args): + def last_currentsong(self): + return self._currentsong.copy() + + def last_status(self): + if time.time() - self._time >= 2: + self.timer_callback() + return self._status.copy() + + def _update_properties(self): + old_status = self._status + old_position = self._position + old_time = self._time + self._currentsong = self.currentsong() + self._status = new_status = self.status() + self._time = new_time = int(time.time()) + + if not new_status: + return + + if 'elapsed' in new_status: + new_position = float(new_status['elapsed']) + elif 'time' in new_status: + new_position = int(new_status['time'].split(':')[0]) + else: + new_position = 0 + + self._position = new_position + + # "player" subsystem + + if old_status['state'] != new_status['state']: + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'PlaybackStatus') + + if old_status.get('songid', None) == new_status.get('songid', None): + if new_status['state'] == 'play': + expected_position = old_position + (new_time - old_time) + else: + expected_position = old_position + if abs(new_position - expected_position) > 0.6: + logger.debug("Expected pos %r, actual %r, diff %r" % ( + expected_position, new_position, new_position - expected_position)) + logger.debug("Old position was %r at %r (%r seconds ago)" % ( + old_position, old_time, new_time - old_time)) + self._dbus_service.Seeked(new_position * 1000000) + + else: + metadata = self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'Metadata') + if self._params['notify']: + uri = 'sound' + if 'mpris:artUrl' in metadata: + uri = metadata['mpris:artUrl'] + title = 'Unknown Title' + if 'xesam:title' in metadata: + title = metadata['xesam:title'] + artist = 'Unknown Artist' + if 'xesam:artist' in metadata: + artist = ", ".join(metadata['xesam:artist']) + notification.notify(title, _('by %s') % artist, uri) + + # "mixer" subsystem + + if old_status['volume'] != new_status['volume']: + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'Volume') + + # "options" subsystem + # also triggered if consume, crossfade or ReplayGain are updated + + if old_status['random'] != new_status['random']: + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'Shuffle') + + if (old_status['repeat'] != new_status['repeat'] + or old_status.get('single', 0) != new_status.get('single', 0)): + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'LoopStatus') + + if ("nextsongid" in old_status) != ("nextsongid" in new_status): + self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', + 'CanGoNext') + + ## Media keys + + def setup_mediakeys(self): + self.register_mediakeys() + self._dbus_obj = self._bus.get_object("org.freedesktop.DBus", + "/org/freedesktop/DBus") + self._dbus_obj.connect_to_signal("NameOwnerChanged", + self.gsd_name_owner_changed_callback, + arg0="org.gnome.SettingsDaemon") + + def register_mediakeys(self): try: - return mpd.MPDClient._execute(self, command, args) - except (socket.error, mpd.MPDError, socket.timeout): - self.reconnect() - return False + gsd_object = self._bus.get_object("org.gnome.SettingsDaemon", + "/org/gnome/SettingsDaemon/MediaKeys") + gsd_object.GrabMediaPlayerKeys("mpDris2", 0, + dbus_interface="org.gnome.SettingsDaemon.MediaKeys") + except: + logger.warning("Failed to connect to GNOME Settings Daemon. Media keys won't work.") + else: + self._bus.remove_signal_receiver(self.mediakey_callback) + gsd_object.connect_to_signal("MediaPlayerKeyPressed", self.mediakey_callback) + + def gsd_name_owner_changed_callback(self, bus_name, old_owner, new_owner): + if bus_name == "org.gnome.SettingsDaemon" and new_owner != "": + def reregister(): + logger.debug("Re-registering with GNOME Settings Daemon (owner %s)" % new_owner) + self.register_mediakeys() + return False + # Timeout is necessary since g-s-d takes some time to load all plugins. + gobject.timeout_add(600, reregister) + + ## Compatibility functions + + # Fedora 17 still has python-mpd 0.2, which lacks fileno(). + if not hasattr(mpd.MPDClient, "fileno"): + def fileno(self): + if self._sock is None: + raise mpd.ConnectionError("Not connected") + return self._sock.fileno() + + if not hasattr(mpd.MPDClient, "_write_command") \ + and hasattr(mpd.MPDClient, "_writecommand"): + def _write_command(self, *args): + return self._writecommand(*args) + + if not hasattr(mpd.MPDClient, "_fetch_object") \ + and hasattr(mpd.MPDClient, "_getobject"): + def _fetch_object(self, *args): + return self._getobject(*args) + + if not hasattr(mpd.MPDClient, "_fetch_objects") \ + and hasattr(mpd.MPDClient, "_getobjects"): + def _fetch_objects(self, *args): + return self._getobjects(*args) + + # Catch connection errors when calling mpd client methods + if hasattr(mpd.MPDClient, "_execute"): + def _execute(self, command, args): + try: + was_idle = self.idle_leave() + logger.debug("Sending command %r (during idle? %r)" % (command, was_idle)) + r = mpd.MPDClient._execute(self, command, args) + if was_idle: + self.idle_enter() + return r + except (socket.error, mpd.MPDError, socket.timeout): + self.reconnect() + return False + elif hasattr(mpd.MPDClient, "_docommand"): + # Again for python-mpd 0.2 + def _docommand(self, command, args, retval): + try: + was_idle = self.idle_leave() + logger.debug("Sending command %r (during idle? %r)" % (command, was_idle)) + r = mpd.MPDClient._docommand(self, command, args, retval) + if was_idle: + self.idle_enter() + return r + except (socket.error, mpd.MPDError, socket.timeout): + self.reconnect() + return False + class Notify: def __init__(self, params): @@ -381,19 +593,19 @@ if pynotify.init(identity): self._notification = pynotify.Notification("", "", "") else: - logger.error('Failed to init libnotify; disabling' \ - 'notifications') + logger.error('Failed to init libnotify; disabling' + 'notifications') self._notification = False else: self._notification = False - def notify(self, title, body, uri = ''): + def notify(self, title, body, uri=''): """ Issue a new notification """ if self._notification: self._notification = pynotify.Notification(title, body, uri) self._notification.show() - def rnotify(self, title, body, uri = ''): + def rnotify(self, title, body, uri=''): """ Replace current notification """ if self._notification: self._notification.update(title, body, uri) @@ -408,18 +620,39 @@ __introspect_interface = "org.freedesktop.DBus.Introspectable" __prop_interface = dbus.PROPERTIES_IFACE - def __init__(self, bus, path = ""): + def __init__(self, bus, path=""): dbus.service.Object.__init__(self, dbus.SessionBus(), - MPRISInterface.__path) + MPRISInterface.__path) self.path = path - self.aquire_name() - def aquire_name(self): + self._bus = dbus.SessionBus() + self._uname = self._bus.get_unique_name() + self._dbus_obj = self._bus.get_object("org.freedesktop.DBus", + "/org/freedesktop/DBus") + self._dbus_obj.connect_to_signal("NameOwnerChanged", + self._name_owner_changed_callback, + arg0=self.__name) + + self.acquire_name() + + def _name_owner_changed_callback(self, name, old_owner, new_owner): + if name == self.__name and old_owner == self._uname and new_owner != "": + try: + pid = self._dbus_obj.GetConnectionUnixProcessID(new_owner) + except: + pid = None + logger.info("Replaced by %s (PID %s)" % (new_owner, pid or "unknown")) + loop.quit() + + def acquire_name(self): self._bus_name = dbus.service.BusName(MPRISInterface.__name, - bus=dbus.SessionBus()) + bus=self._bus, + allow_replacement=True, + replace_existing=True) def release_name(self): - del self._bus_name + if hasattr(self, "_bus_name"): + del self._bus_name __root_interface = "org.mpris.MediaPlayer2" __root_props = { @@ -433,37 +666,52 @@ } def __get_playback_status(): - status = mpd_wrapper.status() + status = mpd_wrapper.last_status() return {'play': 'Playing', 'pause': 'Paused', 'stop': 'Stopped'}[status['state']] def __set_loop_status(value): - if str(value) == "Playlist": + if value == "Playlist": mpd_wrapper.repeat(1) - elif str(value) == "None": + if mpd_wrapper._can_single: + mpd_wrapper.single(0) + elif value == "Track": + if mpd_wrapper._can_single: + mpd_wrapper.repeat(1) + mpd_wrapper.single(1) + elif value == "None": mpd_wrapper.repeat(0) + if mpd_wrapper._can_single: + mpd_wrapper.single(0) else: - raise dbus.exceptions.DBusException("Loop mode not supported") + raise dbus.exceptions.DBusException("Loop mode %r not supported" % + value) return def __get_loop_status(): - status = mpd_wrapper.status() - return ("None", "Playlist")[int(status['repeat'])] + status = mpd_wrapper.last_status() + if int(status['repeat']) == 1: + if int(status.get('single', 0)) == 1: + return "Track" + else: + return "Playlist" + else: + return "None" def __set_shuffle(value): mpd_wrapper.random(value) return def __get_shuffle(): - if mpd_wrapper.status()['random'] == '1': + if int(mpd_wrapper.last_status()['random']) == 1: return True else: return False def __get_metadata(): - return format_metadata(mpd_wrapper.currentsong()) + return format_metadata(mpd_wrapper.last_currentsong()) def __get_volume(): - vol = float(mpd_wrapper.status()['volume']) + vol = float(mpd_wrapper.last_status()['volume']) if vol > 0: return vol / 100.0 else: @@ -475,7 +723,7 @@ return def __get_position(): - status = mpd_wrapper.status() + status = mpd_wrapper.last_status() if 'time' in status: current, end = status['time'].split(':') return dbus.Int64((int(current) * 1000000)) @@ -483,7 +731,7 @@ return dbus.Int64(0) def __can_next(): - if 'nextsongid' in mpd_wrapper.status(): + if 'nextsongid' in mpd_wrapper.last_status(): return True else: return False @@ -511,7 +759,8 @@ __prop_mapping = { __player_interface: __player_props, - __root_interface: __root_props} + __root_interface: __root_props, + } @dbus.service.method(__introspect_interface) def Introspect(self): @@ -519,7 +768,7 @@ @dbus.service.signal(__prop_interface, signature="sa{sv}as") def PropertiesChanged(self, interface, changed_properties, - invalidated_properties): + invalidated_properties): pass @dbus.service.method(__prop_interface, @@ -543,7 +792,8 @@ read_props = {} props = self.__prop_mapping[interface] for key, (getter, setter) in props.iteritems(): - if callable(getter): getter = getter() + if callable(getter): + getter = getter() read_props[key] = getter return read_props @@ -615,16 +865,18 @@ offset = int(offset) / 1000000 if current + offset <= end: position = current + offset - if position < 0: position = 0 + if position < 0: + position = 0 mpd_wrapper.seekid(int(status['songid']), position) self.Seeked(position * 1000000) return @dbus.service.method(__player_interface, in_signature='ox', out_signature='') def SetPosition(self, trackid, position): - song = mpd_wrapper.currentsong() + song = mpd_wrapper.last_currentsong() # FIXME: use real dbus objects - if str(trackid) != '/org/mpris/MediaPlayer2/Track/%s' % song['id']: return + if str(trackid) != '/org/mpris/MediaPlayer2/Track/%s' % song['id']: + return # Convert position to seconds position = int(position) / 1000000 if position <= int(song['time']): @@ -642,90 +894,110 @@ # TODO return + # Handle signals more gracefully def handle_sigint(signum, frame): logger.debug('Caught SIGINT, exiting.') loop.quit() -def format_metadata(metadata): - """http://xmms2.org/wiki/MPRIS_Metadata""" - if 'id' in metadata: - metadata['mpris:trackid'] = "/org/mpris/MediaPlayer2/Track/%s" % metadata['id'] +def find_cover(song_url, metadata): + if song_url.startswith('file://'): + # Look in song directory for common album cover files + song_dir = os.path.dirname(song_url[7:]) + if os.path.exists(song_dir): + for f in os.listdir(song_dir): + if f.startswith(covers_names) and f.endswith(covers_exts): + return 'file://' + os.path.join(song_dir, f) + + # Search the shared cover directories + if 'xesam:artist' in metadata and 'xesam:album' in metadata: + artist = ",".join(metadata['xesam:artist']) + album = metadata['xesam:album'] + for template in downloaded_covers: + f = os.path.expanduser(template % (artist, album)) + if os.path.exists(f): + return 'file://' + f - if 'time' in metadata: - metadata['mpris:length'] = int(metadata['time']) * 1000000 + return None - if 'date' in metadata: - if len(metadata['date']) is 4: - metadata['xesam:contentCreated'] = metadata['date'] - else: - metadata['xesam:contentCreated'] = metadata['date'][0:4] - if 'track' in metadata: - if type(metadata['track']) == list and len(metadata['track']) > 0: - track = str(metadata['track'][0]) +def format_metadata(mpd_meta): + """ + Translate metadata returned by MPD to the MPRIS v2 syntax. + http://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata + """ + + metadata = {} + + for tag in ('album', 'title'): + if tag in mpd_meta: + metadata['xesam:%s' % tag] = mpd_meta[tag] + + if 'id' in mpd_meta: + metadata['mpris:trackid'] = "/org/mpris/MediaPlayer2/Track/%s" % \ + mpd_meta['id'] + + if 'time' in mpd_meta: + metadata['mpris:length'] = int(mpd_meta['time']) * 1000000 + + if 'date' in mpd_meta: + metadata['xesam:contentCreated'] = mpd_meta['date'][0:4] + + if 'track' in mpd_meta: + # TODO: Is it even *possible* for mpd_meta['track'] to be a list? + if type(mpd_meta['track']) == list and len(mpd_meta['track']) > 0: + track = str(mpd_meta['track'][0]) else: - track = str(metadata['track']) - if re.match('^([0-9]+).*', track): - metadata['xesam:trackNumber'] = int(re.match('^([0-9]+).*', track).group(1)) + track = str(mpd_meta['track']) + + m = re.match('^([0-9]+)', track) + if m: + metadata['xesam:trackNumber'] = int(m.group(1)) # Ensure the integer is signed 32bit if metadata['xesam:trackNumber'] & 0x80000000: metadata['xesam:trackNumber'] += -0x100000000 else: metadata['xesam:trackNumber'] = 0 - if 'disc' in metadata: - if type(metadata['disc']) == list and len(metadata['disc']) > 0: - disc = str(metadata['disc'][0]) - else: - disc = str(metadata['disc']) - if re.match('^([0-9]+).*', disc): - metadata['xesam:discNumber'] = int(re.match('^([0-9]+).*', disc).group(1)) - - if 'artist' in metadata: - metadata['xesam:artist'] = [metadata['artist'],] - - if 'composer' in metadata: - metadata['xesam:composer'] = [metadata['composer'],] - - mpd_tags = ('album', 'title') - for tag in mpd_tags: - if tag in metadata: - metadata['xesam:%s' % tag] = metadata[tag] - - if 'file' in metadata: - file = metadata['file'] - if len([ x for x in urlhandlers if file.startswith(x) ]) == 0: - file = os.path.join(params['music_dir'], file) - metadata['xesam:url'] = file - if file.startswith('file://'): - basedirpath = os.path.dirname(file).replace('file://', '') - cover = False - if os.path.exists(basedirpath): - for f in os.listdir(basedirpath): - if f in local_covers: - cover = 'file://' + os.path.join(basedirpath, f) - if not cover and 'artist' in metadata and 'album' in metadata: - for f in downloaded_covers: - f = os.path.expanduser(os.path.join('~', f % \ - (metadata['artist'], metadata['album']))) - if os.path.exists(f): - cover = 'file://' + f - if cover: - metadata['mpris:artUrl'] = cover + if 'disc' in mpd_meta: + # TODO: Same as above. When is it a list? + if type(mpd_meta['disc']) == list and len(mpd_meta['disc']) > 0: + disc = str(mpd_meta['disc'][0]) + else: + disc = str(mpd_meta['disc']) + + m = re.match('^([0-9]+)', disc) + if m: + metadata['xesam:discNumber'] = int(m.group(1)) + + if 'artist' in mpd_meta: + if type(mpd_meta['artist']) == list: + metadata['xesam:artist'] = mpd_meta['artist'] + else: + metadata['xesam:artist'] = [mpd_meta['artist']] + + if 'composer' in mpd_meta: + if type(mpd_meta['composer']) == list: + metadata['xesam:composer'] = mpd_meta['composer'] + else: + metadata['xesam:composer'] = [mpd_meta['composer']] + + if 'file' in mpd_meta: + song_url = mpd_meta['file'] + if not any([song_url.startswith(prefix) for prefix in urlhandlers]): + song_url = os.path.join(params['music_dir'], song_url) + metadata['xesam:url'] = song_url + cover = find_cover(song_url, metadata) + if cover: + metadata['mpris:artUrl'] = cover # Stream: populate some missings tags with stream's name - if 'name' in metadata: + if 'name' in mpd_meta: if 'xesam:title' not in metadata: - metadata['xesam:title'] = metadata['name'] + metadata['xesam:title'] = mpd_meta['name'] elif 'xesam:album' not in metadata: - metadata['xesam:album'] = metadata['name'] - - surplus_tags = set(metadata.keys()).difference(set(allowed_tags.keys())) - # Remove surplus tags - for tag in surplus_tags: - del metadata[tag] + metadata['xesam:album'] = mpd_meta['name'] # Cast metadata to the correct type, or discard it for key, value in metadata.items(): @@ -733,30 +1005,58 @@ metadata[key] = allowed_tags[key](value) except ValueError: del metadata[key] - logger.error("Can't cast value %s to %s" % \ - (value, allowed_tags[key])) + logger.error("Can't cast value %r to %s" % + (value, allowed_tags[key])) return dbus.Dictionary(metadata, signature='sv') + +def each_xdg_config(suffix): + """ + Return each location matching XDG_CONFIG_DIRS/suffix in descending + priority order. + """ + config_home = os.environ.get('XDG_CONFIG_HOME', + os.path.expanduser('~/.config')) + config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':') + return ([os.path.join(config_home, suffix)] + + [os.path.join(d, suffix) for d in config_dirs]) + + +def open_first_xdg_config(suffix): + """ + Try to open each location matching XDG_CONFIG_DIRS/suffix as a file. + Return the first that can be opened successfully, or None. + """ + for filename in each_xdg_config(suffix): + try: + f = open(filename, 'r') + except IOError: + pass + else: + return f + else: + return None + + def find_music_dir(): if 'XDG_MUSIC_DIR' in os.environ: return os.environ['XDG_MUSIC_DIR'] - conf = os.path.expanduser('~/.config/user-dirs.dirs') - try: - for line in open(conf, 'r'): + conf = open_first_xdg_config('user-dirs.dirs') + if conf is not None: + for line in conf: if not line.startswith('XDG_MUSIC_DIR='): continue # use shlex to handle "shell escaping" - path = shlex.split(line)[0][14:] + path = shlex.split(line[14:])[0] if path.startswith('$HOME/'): return os.path.expanduser('~' + path[5:]) elif path.startswith('/'): return path else: - continue # other forms are not supported - except IOError: - pass + # other forms are not supported + break paths = '~/Music', '~/music' for path in map(os.path.expanduser, paths): @@ -765,6 +1065,7 @@ return None + def usage(params): print """\ Usage: %(progname)s [OPTION]... [MPD_HOST] [MPD_PORT] @@ -782,10 +1083,11 @@ if __name__ == '__main__': DBusGMainLoop(set_as_default=True) - path = find_music_dir() + music_dir = find_music_dir() config = ConfigParser.SafeConfigParser() - config.read(['/etc/mpDris2.conf', os.path.expanduser('~/.config/mpDris2/mpDris2.conf')]) + config.read(['/etc/mpDris2.conf'] + + list(reversed(each_xdg_config('mpDris2/mpDris2.conf')))) if config.has_option('Connection', 'host'): params['host'] = config.get('Connection', 'host') @@ -794,13 +1096,17 @@ if config.has_option('Connection', 'password'): params['password'] = config.get('Connection', 'password') if config.has_option('Connection', 'music_dir'): - path = config.get('Connection', 'music_dir') + music_dir = config.get('Connection', 'music_dir') if 'MPD_HOST' in os.environ: params['host'] = os.environ['MPD_HOST'] if 'MPD_PORT' in os.environ: params['port'] = os.environ['MPD_PORT'] + for bling in ['mmkeys', 'notify']: + if config.has_option('Bling', bling): + params[bling] = config.getboolean('Bling', bling) + try: (opts, args) = getopt.getopt(sys.argv[1:], 'hdp:', ['help', 'debug', 'path=']) except getopt.GetoptError, (msg, opt): @@ -817,7 +1123,7 @@ usage(params) sys.exit() elif opt in ['-p', '--path']: - path = arg + music_dir = arg elif opt in ['-d', '--debug']: log_level = logging.DEBUG @@ -837,17 +1143,19 @@ if '@' in params['host']: params['password'], params['host'] = params['host'].split('@', 1) - if path and os.path.exists(path): - logger.info('Using %s as music library path' % path) - if not path.startswith('file://'): - params['music_dir'] = 'file://' + path + if music_dir: + # Ensure that music_dir starts with an URL scheme. + if not re.match('^[0-9A-Za-z+.-]+://', music_dir): + music_dir = 'file://' + music_dir + if music_dir.startswith('file://'): + if not os.path.exists(music_dir[7:]): + logger.error('Music library path %s does not exist!' % music_dir) + # Non-local URLs can still be useful to MPRIS clients, so accept them. + params['music_dir'] = music_dir + logger.info('Using %s as music library path.' % music_dir) else: - logger.warning('By not supplying a path for the music library ' \ - 'this program will break the MPRIS specification!') - - for bling in ['mmkeys','notify']: - if config.has_option('Bling', bling): - params[bling] = config.getboolean('Bling', bling) + logger.warning('By not supplying a path for the music library ' + 'this program will break the MPRIS specification!') loop = gobject.MainLoop() signal.signal(signal.SIGINT, handle_sigint)