[gnome-music/search] Rebased on master



commit af7f7a4ddeedfb721443dcb8e26e255dd101deb6
Author: Vadim Rutkovsky <vrutkovs redhat com>
Date:   Thu Jun 27 18:04:55 2013 +0200

    Rebased on master

 AUTHORS.in                     |   21 +++
 Makefile.am                    |   26 +++-
 autogen.sh                     |    1 +
 data/Makefile.am               |    2 +-
 libgd                          |    2 +-
 src/application.js             |   62 ++++++++
 src/grilo.js                   |    4 +-
 src/player.js                  |  322 +++++++++++++++++++++++++++++++++++++++-
 src/toolbar.js                 |   17 ++
 src/window.js                  |   42 +++++-
 tests/Makefile.am              |   29 ++--
 tests/tests_albumArt.js        |    6 +-
 tests/tests_albumPlayback.js   |    6 +-
 tests/tests_artistsPlayback.js |    6 +-
 14 files changed, 504 insertions(+), 42 deletions(-)
---
diff --git a/AUTHORS.in b/AUTHORS.in
new file mode 100644
index 0000000..0d897b1
--- /dev/null
+++ b/AUTHORS.in
@@ -0,0 +1,21 @@
+    GNOME Music Authors
+    ===================
+
+GNOME Music was initially written by:
+
+    César García Tapia <cesar garcia tapia openshine com>
+    Seif Lotfy <seif lotfy com>
+    Vadim Rutkovsky <vrutkovs redhat com>
+
+GNOME Music is currently maintained by:
+
+    Seif Lotfy <seif lotfy com>
+    Vadim Rutkovsky <vrutkovs redhat com>
+
+Patches have been received from:
+
+#authorslist#
+
+    ... send patches to get your name here ...
+
+-- End
diff --git a/Makefile.am b/Makefile.am
index 987dab2..f98e024 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,8 +1,17 @@
 ACLOCAL_AMFLAGS = -I m4 -I libgd
+NULL =
 
 SUBDIRS = po libgd src data tests
 
+EXTRA_DIST = \
+       AUTHORS.in \
+       $(NULL)
+
+CLEANFILES = \
+       $(NULL)
+
 MAINTAINERCLEANFILES = \
+       $(srcdir)/AUTHORS \
        $(srcdir)/INSTALL \
        $(srcdir)/aclocal.m4 \
        $(srcdir)/autoscan.log \
@@ -15,8 +24,21 @@ MAINTAINERCLEANFILES = \
        $(srcdir)/install-sh \
        $(srcdir)/ltmain.sh \
        $(srcdir)/missing \
-       $(srcdir)/mkinstalldirs
+       $(srcdir)/mkinstalldirs \
+       $(NULL)
+
+GITIGNOREFILES = \
+       m4 \
+       $(NULL)
+
+dist-hook: gen-AUTHORS
 
-GITIGNOREFILES = m4/
+gen-AUTHORS:
+       $(AM_V_GEN)if test -d $(srcdir)/.git; then                      \
+          out="`cd $(srcdir) && git log --pretty=format:'%aN <%aE>' | sort -u`" && \
+          perl -p -e "s/#authorslist#// and print '$$out'"            \
+            < $(srcdir)/AUTHORS.in > $(distdir)/AUTHORS-tmp &&        \
+          mv -f $(distdir)/AUTHORS-tmp $(distdir)/AUTHORS ;           \
+       fi
 
 -include $(top_srcdir)/git.mk
diff --git a/autogen.sh b/autogen.sh
index 5f239c0..6a36f45 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -23,4 +23,5 @@ which gnome-autogen.sh || {
 test -d m4 || mkdir m4/ ;
 git submodule update --init --recursive ;
 )
+touch AUTHORS
 . gnome-autogen.sh
diff --git a/data/Makefile.am b/data/Makefile.am
index 960a2cf..4e58aa1 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -50,9 +50,9 @@ gsettings_SCHEMAS = org.gnome.Music.gschema.xml
 EXTRA_DIST = \
        $(icon_files) \
        $(resource_files) \
-       CREDITS \
        gnome-music.desktop.in \
        gnome-music.gresource.xml \
+       org.gnome.Music.gschema.xml \
        $(NULL)
 
 CLEANFILES = \
diff --git a/libgd b/libgd
index 3fca3b6..8192ded 160000
--- a/libgd
+++ b/libgd
@@ -1 +1 @@
-Subproject commit 3fca3b6073a25b2c8ed4e16135ff3b17912141b6
+Subproject commit 8192ded5a63aff6f2528576009858bf0d61ec5de
diff --git a/src/application.js b/src/application.js
index fb995ab..73b3157 100644
--- a/src/application.js
+++ b/src/application.js
@@ -40,6 +40,18 @@ var AppState = {
     PLAYLIST_NEW: 5
 };
 
+const MediaPlayer2Iface = <interface name="org.mpris.MediaPlayer2">
+  <method name="Raise"/>
+  <method name="Quit"/>
+  <property name="CanQuit" type="b" access="read"/>
+  <property name="CanRaise" type="b" access="read"/>
+  <property name="HasTrackList" type="b" access="read"/>
+  <property name="Identity" type="s" access="read"/>
+  <property name="DesktopEntry" type="s" access="read"/>
+  <property name="SupportedUriSchemes" type="as" access="read"/>
+  <property name="SupportedMimeTypes" type="as" access="read"/>
+</interface>;
+
 const Application = new Lang.Class({
     Name: 'Music',
     Extends: Gtk.Application,
@@ -52,6 +64,11 @@ const Application = new Lang.Class({
         });
 
         GLib.set_application_name(_("Music"));
+
+        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(MediaPlayer2Iface, this);
+        this._dbusImpl.export(Gio.DBus.session, '/org/mpris/MediaPlayer2');
+
+        Gio.DBus.session.own_name('org.mpris.MediaPlayer2.gnome-music', Gio.BusNameOwnerFlags.REPLACE, null, 
null);
     },
 
     _buildAppMenu: function() {
@@ -116,4 +133,49 @@ const Application = new Lang.Class({
             this.quit();
         }));
     },
+
+    /* MPRIS */
+
+    Raise: function() {
+        this._window.present();
+    },
+
+    Quit: function() {
+        this.quit();
+    },
+
+    get CanQuit() {
+        return true;
+    },
+
+    get CanRaise() {
+        return true;
+    },
+
+    get HasTrackList() {
+        return false;
+    },
+
+    get Identity() {
+        return 'Music';
+    },
+
+    get DesktopEntry() {
+        return 'gnome-music';
+    },
+
+    get SupportedUriSchemes() {
+        return [
+            'file'
+        ];
+    },
+
+    get SupportedMimeTypes() {
+        return [
+            'application/ogg',
+            'audio/x-vorbis+ogg',
+            'audio/x-flac',
+            'audio/mpeg'
+        ];
+    },
 });
diff --git a/src/grilo.js b/src/grilo.js
index e3b55fc..4128f03 100644
--- a/src/grilo.js
+++ b/src/grilo.js
@@ -84,7 +84,7 @@ const Grilo = new Lang.Class({
         options.set_count(count);
         grilo.tracker.query(
             query,
-                [Grl.METADATA_KEY_ID, Grl.METADATA_KEY_TITLE, Grl.METADATA_KEY_ARTIST, 
Grl.METADATA_KEY_CREATION_DATE],
+                [Grl.METADATA_KEY_ID, Grl.METADATA_KEY_TITLE, Grl.METADATA_KEY_ARTIST, 
Grl.METADATA_KEY_ALBUM, Grl.METADATA_KEY_DURATION, Grl.METADATA_KEY_THUMBNAIL, 
Grl.METADATA_KEY_CREATION_DATE],
                 options,
                 Lang.bind(this, callback, null));
     },
@@ -95,7 +95,7 @@ const Grilo = new Lang.Class({
         options.set_flags (Grl.ResolutionFlags.FULL | Grl.ResolutionFlags.IDLE_RELAY);
         grilo.tracker.query(
             query,
-                [Grl.METADATA_KEY_ID, Grl.METADATA_KEY_TITLE, Grl.METADATA_KEY_ARTIST, 
Grl.METADATA_KEY_CREATION_DATE],
+                [Grl.METADATA_KEY_ID, Grl.METADATA_KEY_TITLE, Grl.METADATA_KEY_ARTIST, 
Grl.METADATA_KEY_ALBUM, Grl.METADATA_KEY_DURATION, Grl.METADATA_KEY_THUMBNAIL, 
Grl.METADATA_KEY_CREATION_DATE],
                 options,
                 Lang.bind(this, callback, null));
     },
diff --git a/src/player.js b/src/player.js
index be75412..664ab47 100644
--- a/src/player.js
+++ b/src/player.js
@@ -25,6 +25,7 @@ const Gtk = imports.gi.Gtk;
 const Gd = imports.gi.Gd;
 const Gio = imports.gi.Gio;
 const Gst = imports.gi.Gst;
+const GstAudio = imports.gi.GstAudio;
 const GstPbutils = imports.gi.GstPbutils;
 const GLib = imports.gi.GLib;
 const GObject = imports.gi.GObject;
@@ -46,6 +47,52 @@ const RepeatType = {
     SHUFFLE: 3,
 }
 
+const PropertiesIface = <interface name="org.freedesktop.DBus.Properties">
+<signal name="PropertiesChanged">
+  <arg type="s" direction="out" />
+  <arg type="a{sv}" direction="out" />
+  <arg type="as" direction="out" />
+</signal>
+</interface>;
+const PropertiesProxy = Gio.DBusProxy.makeProxyWrapper(PropertiesIface);
+
+const MediaPlayer2PlayerIface = <interface name="org.mpris.MediaPlayer2.Player">
+  <method name="Next"/>
+  <method name="Previous"/>
+  <method name="Pause"/>
+  <method name="PlayPause"/>
+  <method name="Stop"/>
+  <method name="Play"/>
+  <method name="Seek">
+    <arg direction="in" name="Offset" type="x"/>
+  </method>
+  <method name="SetPosition">
+    <arg direction="in" name="TrackId" type="o"/>
+    <arg direction="in" name="Position" type="x"/>
+  </method>
+  <method name="OpenUri">
+    <arg direction="in" name="Uri" type="s"/>
+  </method>
+  <signal name="Seeked">
+    <arg name="Position" type="x"/>
+  </signal>
+  <property name="PlaybackStatus" type="s" access="read"/>
+  <property name="LoopStatus" type="s" access="readwrite"/>
+  <property name="Rate" type="d" access="readwrite"/>
+  <property name="Shuffle" type="b" access="readwrite"/>
+  <property name="Metadata" type="a{sv}" access="read"/>
+  <property name="Volume" type="d" access="readwrite"/>
+  <property name="Position" type="x" access="read"/>
+  <property name="MinimumRate" type="d" access="read"/>
+  <property name="MaximumRate" type="d" access="read"/>
+  <property name="CanGoNext" type="b" access="read"/>
+  <property name="CanGoPrevious" type="b" access="read"/>
+  <property name="CanPlay" type="b" access="read"/>
+  <property name="CanPause" type="b" access="read"/>
+  <property name="CanSeek" type="b" access="read"/>
+  <property name="CanControl" type="b" access="read"/>
+</interface>;
+
 const Player = new Lang.Class({
     Name: "Player",
 
@@ -73,6 +120,9 @@ const Player = new Lang.Class({
         }));
         this.repeat = this._settings.get_enum('repeat');
 
+        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(MediaPlayer2PlayerIface, this);
+        this._dbusImpl.export(Gio.DBus.session, '/org/mpris/MediaPlayer2');
+
         this.bus.connect("message::state-changed", Lang.bind(this, function(bus, message) {
             // Note: not all state changes are signaled through here, in particular
             // transitions between Gst.State.READY and Gst.State.NULL are never async
@@ -176,7 +226,7 @@ const Player = new Lang.Class({
         case RepeatType.ALL:
             previousTrack = currentTrack.copy();
             if (!this.playlist.iter_previous(previousTrack))
-                nextTrack = this._getIterLast();
+                previousTrack = this._getIterLast();
             break;
 
         case RepeatType.NONE:
@@ -234,8 +284,14 @@ const Player = new Lang.Class({
     },
 
     _syncPrevNext: function() {
-        this.nextBtn.sensitive = this._hasNext();
-        this.prevBtn.sensitive = this._hasPrevious();
+        let hasNext = this._hasNext()
+        let hasPrevious = this._hasPrevious()
+
+        this.nextBtn.sensitive = hasNext;
+        this.prevBtn.sensitive = hasPrevious;
+
+        this._dbusImpl.emit_property_changed('CanGoNext', GLib.Variant.new('b', hasNext));
+        this._dbusImpl.emit_property_changed('CanGoPrevious', GLib.Variant.new('b', hasPrevious));
     },
 
     setPlaying: function(bool) {
@@ -245,6 +301,9 @@ const Player = new Lang.Class({
             this.play();
         else
             this.pause();
+
+        let media = this.playlist.get_value(this.currentTrack, this.playlistField);
+        this.playBtn.set_image(this._pauseImage);
     },
 
     load: function(media) {
@@ -294,11 +353,18 @@ const Player = new Lang.Class({
             this.player.nextUrl = null;
         }
 
+        this._dbusImpl.emit_property_changed('Metadata', GLib.Variant.new('a{sv}', this.Metadata));
+        this._dbusImpl.emit_property_changed('CanPlay', GLib.Variant.new('b', true));
+        this._dbusImpl.emit_property_changed('CanPause', GLib.Variant.new('b', true));
+
         this.emit("playlist-item-changed", this.playlist, this.currentTrack);
         this.emit('current-changed');
     },
 
     play: function() {
+        if (this.playlist == null)
+            return;
+
         if (this.player.get_state(1)[1] != Gst.State.PAUSED)
             this.stop();
 
@@ -308,6 +374,8 @@ const Player = new Lang.Class({
         this._updatePositionCallback();
         if (!this.timeout)
             this.timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, Lang.bind(this, 
this._updatePositionCallback));
+
+        this._dbusImpl.emit_property_changed('PlaybackStatus', GLib.Variant.new('s', 'Playing'));
     },
 
     pause: function () {
@@ -317,6 +385,7 @@ const Player = new Lang.Class({
         }
 
         this.player.set_state(Gst.State.PAUSED);
+        this._dbusImpl.emit_property_changed('PlaybackStatus', GLib.Variant.new('s', 'Paused'));
     },
 
     stop: function() {
@@ -326,25 +395,36 @@ const Player = new Lang.Class({
         }
 
         this.player.set_state(Gst.State.NULL);
+        this._dbusImpl.emit_property_changed('PlaybackStatus', GLib.Variant.new('s', 'Stopped'));
         this.emit('playing-changed');
     },
 
     playNext: function() {
+        if (this.playlist == null)
+            return;
+
+        if (!this.nextBtn.sensitive)
+            return;
+
+        this.stop();
         this.currentTrack = this._getNextTrack();
 
         if (this.currentTrack)
             this.play();
-        else
-            this.stop();
     },
 
     playPrevious: function() {
+        if (this.playlist == null)
+            return;
+
+        if (!this.prevBtn.sensitive)
+            return;
+
+        this.stop();
         this.currentTrack = this._getPreviousTrack();
 
         if (this.currentTrack)
             this.play();
-        else
-            this.stop();
     },
 
     setPlaylist: function (type, id, model, iter, field) {
@@ -478,20 +558,248 @@ const Player = new Lang.Class({
         }
 
         this.repeatBtnImage.icon_name = icon;
+        this._dbusImpl.emit_property_changed('LoopStatus', GLib.Variant.new('s', this.LoopStatus));
+        this._dbusImpl.emit_property_changed('Shuffle', GLib.Variant.new('b', this.Shuffle));
     },
 
     onProgressScaleChangeValue: function(scroll) {
         var seconds = scroll.get_value() / 60;
         if (seconds != this.duration) {
             this.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, seconds * 
1000000000);
+            this._dbusImpl.emit_signal('Seeked', GLib.Variant.new('(x)', [seconds * 1000000]));
         } else {
             let duration = this.player.query_duration(Gst.Format.TIME, null);
             if (duration) {
                 // Rewind a second back before the track end
                 this.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 
duration[1]-1000000000);
+                this._dbusImpl.emit_signal('Seeked', GLib.Variant.new('(x)', 
[(duration[1]-1000000000)/1000]));
             }
         }
         return true;
      },
+
+    /* MPRIS */
+
+    Next: function() {
+        this.playNext();
+    },
+
+    Previous: function() {
+        this.playPrevious();
+    },
+
+    Pause: function() {
+        this.setPlaying(false);
+    },
+
+    PlayPause: function() {
+        if (this.player.get_state(1)[1] == Gst.State.PLAYING){
+            this.setPlaying(false);
+        } else {
+            this.setPlaying(true);
+        }
+    },
+
+    Play: function() {
+        this.setPlaying(true);
+    },
+
+    Stop: function() {
+        this.progressScale.set_value(0);
+        this.progressScale.sensitive = false;
+        this.playBtn.set_image(this._playImage);
+        this.stop();
+    },
+
+    SeekAsync: function(params, invocation) {
+        let [offset] = params;
+
+        let duration = this.player.query_duration(Gst.Format.TIME, null);
+        if (!duration)
+            return;
+
+        if (offset < 0) {
+            offset = 0;
+        }
+
+        if (duration[1] >= offset * 1000) {
+            this.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, offset * 
1000);
+            this._dbusImpl.emit_signal('Seeked', GLib.Variant.new('(x)', [offset]));
+        } else {
+            this.playNext();
+        }
+    },
+
+    SetPositionAsync: function(params, invocation) {
+        let [trackId, position] = params;
+
+        if (this.currentTrack == null)
+            return;
+
+        let media = this.playlist.get_value(this.currentTrack, this.playlistField);
+        if (trackId != '/org/mpris/MediaPlayer2/Track/' + media.get_id())
+            return;
+
+        let duration = this.player.query_duration(Gst.Format.TIME, null);
+        if (duration && position >= 0 && duration[1] >= position * 1000) {
+            this.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, position 
* 1000);
+            this._dbusImpl.emit_signal('Seeked', GLib.Variant.new('(x)', [position]));
+        }
+    },
+
+    OpenUriAsync: function(params, invocation) {
+        let [uri] = params;
+    },
+
+    get PlaybackStatus() {
+        let [ok, state, pending] = this.player.get_state(0);
+        if (ok == Gst.StateChangeReturn.ASYNC)
+            state = pending;
+        else if (ok != Gst.StateChangeReturn.SUCCESS)
+            return 'Stopped';
+
+        if (state == Gst.State.PLAYING) {
+            return 'Playing';
+        } else if (state == Gst.State.PAUSED) {
+            return 'Paused';
+        } else {
+            return 'Stopped';
+        }
+    },
+
+    get LoopStatus() {
+        if (this.repeat == RepeatType.NONE) {
+            return 'None';
+        } else if (this.repeat == RepeatType.SONG) {
+            return 'Track';
+        } else {
+            return 'Playlist';
+        }
+    },
+
+    set LoopStatus(mode) {
+        if (mode == 'None') {
+            this.repeat = RepeatType.NONE;
+        } else if (mode == 'Track') {
+            this.repeat = RepeatType.SONG;
+        } else if (mode == 'Playlist') {
+            this.repeat = RepeatType.ALL;
+        }
+        this._syncRepeatImage();
+    },
+
+    get Rate() {
+        return 1.0;
+    },
+
+    set Rate(rate) {
+    },
+
+    get Shuffle() {
+        return this.repeat == RepeatType.SHUFFLE;
+    },
+
+    set Shuffle(enable) {
+        if (enable && this.repeat != RepeatType.SHUFFLE) {
+            this.repeat = RepeatType.SHUFFLE;
+        } else if (!enable && this.repeat == RepeatType.SHUFFLE) {
+            this.repeat = RepeatType.NONE;
+        }
+        this._syncRepeatImage();
+    },
+
+    get Metadata() {
+        if (this.currentTrack == null)
+            return {};
+
+        let media = this.playlist.get_value(this.currentTrack, this.playlistField);
+        let metadata = {
+            'mpris:trackid': GLib.Variant.new('s', '/org/mpris/MediaPlayer2/Track/' + media.get_id()),
+            'xesam:url': GLib.Variant.new('s', media.get_url()),
+            'mpris:length': GLib.Variant.new('x', media.get_duration()*1000000),
+            'xesam:trackNumber': GLib.Variant.new('i', media.get_track_number()),
+            'xesam:useCount': GLib.Variant.new('i', media.get_play_count()),
+            'xesam:userRating': GLib.Variant.new('d', media.get_rating()),
+        };
+
+        let title = media.get_title();
+        if (title) {
+            metadata['xesam:title'] = GLib.Variant.new('s', title);
+        }
+
+        let album = media.get_album();
+        if (album) {
+            metadata['xesam:album'] = GLib.Variant.new('s', album);
+        }
+
+        let artist = media.get_artist();
+        if (artist) {
+            metadata['xesam:artist'] = GLib.Variant.new('as', [artist]);
+            metadata['xesam:albumArtist'] = GLib.Variant.new('as', [artist]);
+        }
+
+        let genre = media.get_genre();
+        if (genre) {
+            metadata['xesam:genre'] = GLib.Variant.new('as', [genre]);
+        }
+
+        let last_played = media.get_last_played();
+        if (last_played) {
+            metadata['xesam:lastUsed'] = GLib.Variant.new('s', last_played);
+        }
+
+        let thumbnail = media.get_thumbnail();
+        if (thumbnail) {
+            metadata['mpris:artUrl'] = GLib.Variant.new('s', thumbnail);
+        }
+
+        return metadata;
+    },
+
+    get Volume() {
+        return this.player.get_volume(GstAudio.StreamVolumeFormat.LINEAR);
+    },
+
+    set Volume(rate) {
+        this.player.set_volume(GstAudio.StreamVolumeFormat.LINEAR, rate);
+        this._dbusImpl.emit_property_changed('Volume', GLib.Variant.new('d', rate));
+    },
+
+    get Position() {
+        return this.player.query_position(Gst.Format.TIME, null)[1]/1000;
+    },
+
+    get MinimumRate() {
+        return 1.0;
+    },
+
+    get MaximumRate() {
+        return 1.0;
+    },
+
+    get CanGoNext() {
+        return this._hasNext();
+    },
+
+    get CanGoPrevious() {
+        return this._hasPrevious();
+    },
+
+    get CanPlay() {
+        return this.currentTrack != null;
+    },
+
+    get CanPause() {
+        return this.currentTrack != null;
+    },
+
+    get CanSeek() {
+        return true;
+    },
+
+    get CanControl() {
+        return true;
+    },
+
 });
 Signals.addSignalMethods(Player.prototype);
diff --git a/src/toolbar.js b/src/toolbar.js
index fca41ee..71dc9cd 100644
--- a/src/toolbar.js
+++ b/src/toolbar.js
@@ -49,6 +49,7 @@ const Toolbar = new Lang.Class({
         this._addBackButton();
         this._addSearchButton();
         this._addSelectButton();
+        this._addCloseButton();
     },
 
     set_stack: function(stack) {
@@ -91,6 +92,14 @@ const Toolbar = new Lang.Class({
             this._backButton.show();
         else
             this._backButton.hide();
+
+        if (this._selectionMode) {
+            this._closeSeparator.hide();
+            this._closeButton.hide();
+        } else {
+            this._closeSeparator.show();
+            this._closeButton.show();
+        }
     },
 
     _addBackButton: function() {
@@ -115,6 +124,14 @@ const Toolbar = new Lang.Class({
                                                         label: _("Select") });
         this.pack_end(this._selectButton);
         this._selectButton.show();
+    },
+
+    _addCloseButton: function() {
+        this._closeSeparator = new Gtk.Separator({ orientation: Gtk.Orientation.VERTICAL });
+        this.pack_end(this._closeSeparator);
+
+        this._closeButton = new Gd.HeaderCloseButton();
+        this.pack_end(this._closeButton);
     }
 });
 Signals.addSignalMethods(Toolbar.prototype);
diff --git a/src/window.js b/src/window.js
index aed4eda..d695469 100644
--- a/src/window.js
+++ b/src/window.js
@@ -44,14 +44,53 @@ const MainWindow = new Lang.Class({
             application: app,
             title: _('Music'),
             window_position: Gtk.WindowPosition.CENTER,
-            hide_titlebar_when_maximized: true
         });
+        this.connect('focus-in-event', Lang.bind(this, this._windowsFocusCb));
 
         let settings = new Gio.Settings({ schema: 'org.gnome.Music' });
         this.add_action(settings.create_action('repeat'));
 
         this.set_size_request(887, 640);
         this._setupView();
+
+        this.proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, null),
+                                            Gio.DBusProxyFlags.NONE,
+                                            null,
+                                            'org.gnome.SettingsDaemon',
+                                            '/org/gnome/SettingsDaemon/MediaKeys',
+                                            'org.gnome.SettingsDaemon.MediaKeys',
+                                            null);
+        this.proxy.call_sync('GrabMediaPlayerKeys',
+                             GLib.Variant.new('(su)', 'Music'),
+                             Gio.DBusCallFlags.NONE,
+                             -1,
+                             null);
+        this.proxy.connect('g-signal', Lang.bind(this, this._handleMediaKeys));
+    },
+
+    _windowsFocusCb: function(window, event) {
+        this.proxy.call_sync('GrabMediaPlayerKeys',
+                             GLib.Variant.new('(su)', 'Music'),
+                             Gio.DBusCallFlags.NONE,
+                             -1,
+                             null);
+    },
+
+    _handleMediaKeys: function(proxy, sender, signal, parameters) {
+        if (signal != 'MediaPlayerKeyPressed') {
+            log ('Received an unexpected signal \'%s\' from media player'.format(signal));
+            return;
+        }
+
+        let key = parameters.get_child_value(1).get_string()[0];
+        if (key == 'Play')
+            this.player.PlayPause();
+        else if (key == 'Stop')
+            this.player.Stop();
+        else if (key == 'Next')
+            this.player.Next();
+        else if (key == 'Previous')
+            this.player.Previous();
     },
 
     _setupView: function () {
@@ -63,6 +102,7 @@ const MainWindow = new Lang.Class({
         this.player = new Player.Player();
 
         this.toolbar = new Toolbar.Toolbar();
+        this.set_titlebar(this.toolbar);
         this._stack = new Gtk.Stack({
             transition_type: Gtk.StackTransitionType.CROSSFADE,
             transition_duration: 100,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e0c46a1..ae347dd 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,18 +1,21 @@
 TESTS = \
-    tests_albumArt.js \
-    tests_albumPlayback.js \
-    tests_artistsPlayback.js \
-    $(NULL)
+       tests_albumArt.js \
+       tests_albumPlayback.js \
+       tests_artistsPlayback.js \
+       $(NULL)
+
+EXTRA_DIST = $(TESTS)
+
+TESTS_ENVIRONMENT = \
+       LD_LIBRARY_PATH=$(top_builddir)/libgd/.libs:$$LD_LIBRARY_PATH \
+       GI_TYPELIB_PATH=$(top_builddir)/libgd:$$GI_TYPELIB_PATH
 
 check-TESTS:
-       @RUN_TESTS_ENV_VARS= \
-            LD_LIBRARY_PATH=$(top_srcdir)/libgd/.libs:$$LD_LIBRARY_PATH \
-            GI_TYPELIB_PATH=$(top_srcdir)/libgd:$$GI_TYPELIB_PATH ; \
-        for test in $(TESTS) ; do \
-          echo "Running suite $$test" ; \
-          $(RUN_TESTS_ENV_VARS) gjs $$test ; \
-          status=$$? ; \
-          if [[ $$status -ne 0 ]] ; then exit 1 ; fi ; \
-        done
+       for test in $(TESTS) ; do \
+               echo "Running suite $$test" ; \
+               $(TESTS_ENVIRONMENT) gjs --include-path=$(top_srcdir)/src $(srcdir)/$$test ; \
+               status=$$? ; \
+               if [[ $$status -ne 0 ]] ; then exit 1 ; fi ; \
+       done
 
 -include $(top_srcdir)/git.mk
diff --git a/tests/tests_albumArt.js b/tests/tests_albumArt.js
index 224a115..e0835e5 100755
--- a/tests/tests_albumArt.js
+++ b/tests/tests_albumArt.js
@@ -4,11 +4,7 @@ if (!('assertEquals' in this)) { /* allow running this test standalone */
     gjstestRun = function() { return imports.jsUnit.gjstestRun(window); };
 }
 
-imports.searchPath.unshift('..');
-imports.searchPath.unshift('../src');
-imports.searchPath.unshift('../libgd');
-imports.searchPath.unshift('../data');
-const AlbumArtCache = imports.src.albumArtCache.AlbumArtCache
+const AlbumArtCache = imports.albumArtCache.AlbumArtCache
 const GLib = imports.gi.GLib;
 const Lang = imports.lang;
 
diff --git a/tests/tests_albumPlayback.js b/tests/tests_albumPlayback.js
index 31eb330..b927eec 100644
--- a/tests/tests_albumPlayback.js
+++ b/tests/tests_albumPlayback.js
@@ -4,10 +4,6 @@ if (!('assertEquals' in this)) { /* allow running this test standalone */
     gjstestRun = function() { return imports.jsUnit.gjstestRun(window); };
 }
 
-imports.searchPath.unshift('..');
-imports.searchPath.unshift('../src');
-imports.searchPath.unshift('../libgd');
-imports.searchPath.unshift('../data');
 const Gio = imports.gi.Gio;
 const Gtk = imports.gi.Gtk;
 const Gdk = imports.gi.Gdk;
@@ -37,7 +33,7 @@ function getAlbumView() {
     let player = new Player.Player();
     let stack = new Gtk.Stack();
     toolbar.set_stack(stack);
-    view = new AlbumView(toolbar, player);
+    let view = new AlbumView(toolbar, player);
     stack.add_titled(view, "Albums", "Albums");
     stack.set_visible_child_name('Albums');
     return view;
diff --git a/tests/tests_artistsPlayback.js b/tests/tests_artistsPlayback.js
index 3393c31..8a73e17 100644
--- a/tests/tests_artistsPlayback.js
+++ b/tests/tests_artistsPlayback.js
@@ -4,10 +4,6 @@ if (!('assertEquals' in this)) { /* allow running this test standalone */
     gjstestRun = function() { return imports.jsUnit.gjstestRun(window); };
 }
 
-imports.searchPath.unshift('..');
-imports.searchPath.unshift('../src');
-imports.searchPath.unshift('../libgd');
-imports.searchPath.unshift('../data');
 const Gio = imports.gi.Gio;
 const Gtk = imports.gi.Gtk;
 const Gdk = imports.gi.Gdk;
@@ -37,7 +33,7 @@ function getArtistView() {
     let player = new Player.Player();
     let stack = new Gtk.Stack();
     toolbar.set_stack(stack);
-    view = new ArtistView(toolbar, player);
+    let view = new ArtistView(toolbar, player);
     stack.add_titled(view, "Artists", "Artists");
     stack.set_visible_child_name('Artists');
     return view;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]