>From a41fbf12ba614e2fe6de28085ff9a561b817c4b8 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Sun, 1 Sep 2013 16:18:38 +0200 Subject: [PATCH 1/4] Add support for notifications Add a NotificationManager object that watches the Player and keeps a resident notification to control the application. https://bugzilla.gnome.org/show_bug.cgi?id=702377 --- gnomemusic/Makefile.am | 1 + gnomemusic/application.py | 7 ++- gnomemusic/notification.py | 149 +++++++++++++++++++++++++++++++++++++++++++++ gnomemusic/player.py | 6 +- 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 gnomemusic/notification.py diff --git a/gnomemusic/Makefile.am b/gnomemusic/Makefile.am index 5fd6c97..b2b8c99 100644 --- a/gnomemusic/Makefile.am +++ b/gnomemusic/Makefile.am @@ -6,6 +6,7 @@ app_PYTHON = \ __init__.py \ player.py \ mpris.py \ + notification.py \ toolbar.py \ view.py \ grilo.py \ diff --git a/gnomemusic/application.py b/gnomemusic/application.py index c201368..e34c8d8 100644 --- a/gnomemusic/application.py +++ b/gnomemusic/application.py @@ -31,10 +31,11 @@ # delete this exception statement from your version. -from gi.repository import Gtk, Gio, GLib, Gdk +from gi.repository import Gtk, Gio, GLib, Gdk, Notify from gettext import gettext as _ from gnomemusic.window import Window from gnomemusic.mpris import MediaPlayer2Service +from gnomemusic.notification import NotificationManager class Application(Gtk.Application): @@ -98,6 +99,8 @@ class Application(Gtk.Application): def do_startup(self): Gtk.Application.do_startup(self) + Notify.init(_("Music")) + self.build_app_menu() def quit(self, action, param): @@ -107,4 +110,6 @@ class Application(Gtk.Application): if not self._window: self._window = Window(self) self.service = MediaPlayer2Service(self) + self._notifications = NotificationManager(self._window.player) + self._window.present() diff --git a/gnomemusic/notification.py b/gnomemusic/notification.py new file mode 100644 index 0000000..6f65090 --- /dev/null +++ b/gnomemusic/notification.py @@ -0,0 +1,149 @@ +# Copyright (c) 2013 Giovanni Campagna +# +# GNOME Music is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GNOME Music is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with GNOME Music; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# The GNOME Music authors hereby grant permission for non-GPL compatible +# GStreamer plugins to be used and distributed together with GStreamer +# and GNOME Music. This permission is above and beyond the permissions +# granted by the GPL license by which GNOME Music is covered. If you +# modify this code, you may extend this exception to your version of the +# code, but you are not obligated to do so. If you do not wish to do so, +# delete this exception statement from your version. + +from gi.repository import Gio, GLib, Grl, Notify + +from gnomemusic.albumArtCache import AlbumArtCache +from gnomemusic import grilo + +from gettext import gettext as _ + +IMAGE_SIZE = 125 + + +class NotificationManager: + def __init__(self, player): + self._player = player + + self._notification = Notify.Notification() + + self._notification.set_category('x-gnome.music') + self._notification.set_hint('action-icons', GLib.Variant('b', True)) + self._notification.set_hint('resident', GLib.Variant('b', True)) + self._notification.set_hint('desktop-entry', GLib.Variant('s', 'gnome-music')) + + self._isPlaying = False + + self._albumArtCache = AlbumArtCache.get_default() + self._symbolicIcon = self._albumArtCache.make_default_icon(IMAGE_SIZE, IMAGE_SIZE) + + self._player.connect('playing-changed', self._on_playing_changed) + self._player.connect('current-changed', self._update_track) + + def _on_playing_changed(self, player): + # this function might be called from one of the action handlers + # from libnotify, and we can't call _set_actions() from there + # (we would free the closure we're currently in and corrupt + # the stack) + GLib.idle_add(self._update_playing) + + def _update_playing(self): + isPlaying = self._player.playing + + if self._isPlaying != isPlaying: + self._isPlaying = isPlaying + self._set_actions(isPlaying) + self._update_track(self._player) + + def _update_track(self, player): + model = self._player.playlist + trackIter = self._player.currentTrack + + if trackIter is None: + self._notification.update(_("Not playing"), None, 'gnome-music') + self._notification.set_hint('image-data', None) + self._notification.show() + else: + trackField = self._player.playlistField + item = model.get_value(trackIter, trackField) + artist = item.get_author() + if artist is None: + artist = item.get_string(Grl.METADATA_KEY_ARTIST) + album = item.get_string(Grl.METADATA_KEY_ALBUM) + + self._notification.update(item.get_title(), + # TRANSLATORS: by refers to the artist, from to the album + _("by %s, from %s") % ('' + artist + '', + '' + album + ''), + 'gnome-music') + + # Try to pass an image path instead of a serialized pixbuf if possible + if item.get_thumbnail(): + self._notification.set_hint('image-path', GLib.Variant('s', item.get_thumbnail())) + self._notification.set_hint('image-data', None) + self._notification.show() + return + + self._albumArtCache.lookup(item, IMAGE_SIZE, IMAGE_SIZE, self._album_art_loaded) + + def _album_art_loaded(self, image, path, data): + if path: + self._notification.set_hint('image-path', GLib.Variant('s', path)) + self._notification.set_hint('image-data', None) + else: + self._notification.set_hint('image-path', None) + + if not image: + image = self._symbolicIcon + + width = image.get_width() + height = image.get_height() + rowStride = image.get_rowstride() + hasAlpha = image.get_has_alpha() + bitsPerSample = image.get_bits_per_sample() + nChannels = image.get_n_channels() + data = image.get_pixels() + + serialized = GLib.Variant('(iiibiiay)', + [width, height, rowStride, hasAlpha, + bitsPerSample, nChannels, data]) + self._notification.set_hint('image-data', serialized) + + self._notification.show() + + def _set_actions(self, playing): + self._notification.clear_actions() + + self._notification.add_action('media-skip-backward', _("Previous"), + self._go_previous, None) + if playing: + self._notification.add_action('media-playback-pause', _("Pause"), + self._pause, None) + else: + self._notification.add_action('media-playback-start', _("Play"), + self._play, None) + self._notification.add_action('media-skip-forward', _("Next"), + self._go_next, None) + + def _go_previous(self, notification, action, data): + self._player.play_previous() + + def _go_next(self, notification, action, data): + self._player.play_next() + + def _play(self, notification, action, data): + self._player.play() + + def _pause(self, notification, action, data): + self._player.pause() diff --git a/gnomemusic/player.py b/gnomemusic/player.py index 4d6db37..60d63d2 100644 --- a/gnomemusic/player.py +++ b/gnomemusic/player.py @@ -56,7 +56,6 @@ class PlaybackStatus: class Player(GObject.GObject): nextTrack = None timeout = None - playing = False __gsignals__ = { 'playing-changed': (GObject.SIGNAL_RUN_FIRST, None, ()), @@ -274,6 +273,10 @@ class Player(GObject.GObject): else: return False + @property + def playing(self): + return self._get_playing() + def _sync_playing(self): image = self._pauseImage if self._get_playing() else self._playImage if self.playBtn.get_image() != image: @@ -345,6 +348,7 @@ class Player(GObject.GObject): self.timeout = GLib.timeout_add(1000, self._update_position_callback) self.emit('playback-status-changed') + self.emit('playing-changed') def pause(self): if self.timeout: -- 1.8.3.1