Beispiel #1
0
    def __init__(self, application_id):
        super().__init__(application_id=application_id,
                         flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.props.resource_base_path = "/org/gnome/Music"
        GLib.set_application_name(_("Music"))
        GLib.set_prgname(application_id)
        GLib.setenv("PULSE_PROP_media.role", "music", True)

        self._init_style()
        self._window = None

        self._log = MusicLogger()
        self._search = Search()

        self._notificationmanager = NotificationManager(self)
        self._coreselection = CoreSelection()
        self._coremodel = CoreModel(self)
        # Order is important: CoreGrilo initializes the Grilo sources,
        # which in turn use CoreModel & CoreSelection extensively.
        self._coregrilo = CoreGrilo(self)

        self._settings = Gio.Settings.new('org.gnome.Music')
        self._lastfm_scrobbler = LastFmScrobbler(self)
        self._player = Player(self)

        InhibitSuspend(self)
        PauseOnSuspend(self._player)
    def __init__(self):
        super().__init__()

        self._log = MusicLogger()

        self._client = None
        self._state = GoaLastFM.State.NOT_AVAILABLE
        self.notify("state")
        self._reset_attributes()
        Goa.Client.new(None, self._new_client_callback)
Beispiel #3
0
def get_media_title(item):
    """Returns the title of the media item.

    :param Grl.Media item: A Grilo Media object
    :return: The title
    :rtype: str
    """

    title = item.get_title()

    if not title:
        url = item.get_url()
        # FIXME: This and the later occurance are user facing strings,
        # but they ideally should never be seen. A media should always
        # contain a URL or we can not play it, in that case it should
        # be removed.
        if url is None:
            return "NO URL"
        file_ = Gio.File.new_for_uri(url)
        try:
            # FIXME: query_info is not async.
            fileinfo = file_.query_info("standard::display-name",
                                        Gio.FileQueryInfoFlags.NONE, None)
        except GLib.Error as error:
            MusicLogger().warning("Error: {}, {}".format(
                error.domain, error.message))
            return "NO URL"
        title = fileinfo.get_display_name()
        title = title.replace("_", " ")

    return title
Beispiel #4
0
    def __init__(self, player, window):
        """Initialize media keys handling

        :param Player player: Player object
        :param Gtk.Window window: Window to grab keys if focused
        """
        super().__init__()

        self._log = MusicLogger()

        self._player = player
        self._window = window

        self._media_keys_proxy = None

        self._init_media_keys_proxy()
Beispiel #5
0
    def __init__(self, player):
        """Initialize pause on supend handling

        :param Player player: Player object
        """
        super().__init__()

        self._log = MusicLogger()

        self._player = player
        self._init_pause_on_suspend()

        self._connection = None
        self._file_descriptor = -1
        self._suspend_proxy = None
        self._previous_state = self._player.props.state
        self._player.connect("notify::state", self._on_player_state_changed)
Beispiel #6
0
    def __init__(self, coreobject, uri):
        """Initialize StoreArtistArt

        :param coreobject: The CoreArtist or CoreAlbum to store art for
        :param string uri: The art uri
        """
        self._coreobject = coreobject

        self._log = MusicLogger()
        self._soup_session = Soup.Session.new()

        if (uri is None or uri == ""):
            self._coreobject.props.thumbnail = "generic"
            return

        cache_dir = GLib.build_filenamev(
            [GLib.get_user_cache_dir(), "media-art"])
        cache_dir_file = Gio.File.new_for_path(cache_dir)
        cache_dir_file.query_info_async(Gio.FILE_ATTRIBUTE_ACCESS_CAN_READ,
                                        Gio.FileQueryInfoFlags.NONE,
                                        GLib.PRIORITY_LOW, None,
                                        self._cache_dir_info_read, uri)
class GoaLastFM(GObject.GObject):
    """Last.fm account handling through GOA
    """
    class State(IntEnum):
        """GoaLastFM account State.

        NOT_AVAILABLE: GOA does not handle Last.fm accounts
        NOT_CONFIGURED: GOA handles Last.fm but no user account has
                        been configured
        DISABLED: a user account exists, but it is disabled
        ENABLED: a user account exists and is enabled
        """
        NOT_AVAILABLE = 0
        NOT_CONFIGURED = 1
        DISABLED = 2
        ENABLED = 3

    def __init__(self):
        super().__init__()

        self._log = MusicLogger()

        self._client = None
        self._state = GoaLastFM.State.NOT_AVAILABLE
        self.notify("state")
        self._reset_attributes()
        Goa.Client.new(None, self._new_client_callback)

    def _reset_attributes(self):
        self._account = None
        self._authentication = None
        self._music_disabled_id = None

    def _new_client_callback(self, source, result):
        try:
            self._client = source.new_finish(result)
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.code,
                                                     error.message))
            return

        manager = self._client.get_manager()

        if manager is None:
            self._log.info("GNOME Online Accounts is unavailable")
            return

        try:
            manager.call_is_supported_provider("lastfm", None,
                                               self._lastfm_is_supported_cb)
        except TypeError:
            self._log.warning("Error: Unable to check if last.fm is supported")

    def _lastfm_is_supported_cb(self, proxy, result):
        try:
            lastfm_supported = proxy.call_is_supported_provider_finish(result)
        except GLib.Error as e:
            self._log.warning(
                "Error: Unable to check if last.fm is supported: {}".format(
                    e.message))
            return

        if lastfm_supported is False:
            return

        self._state = GoaLastFM.State.NOT_CONFIGURED
        self.notify("state")
        self._client.connect("account-added", self._goa_account_added)
        self._client.connect("account-removed", self._goa_account_removed)
        self._find_lastfm_account()

    def _goa_account_added(self, client, obj):
        self._find_lastfm_account()

    def _goa_account_removed(self, client, obj):
        account = obj.get_account()
        if account.props.provider_type == "lastfm":
            self._account.disconnect(self._music_disabled_id)
            self._state = GoaLastFM.State.NOT_CONFIGURED
            self._reset_attributes()
            self.notify("state")

    def _find_lastfm_account(self):
        accounts = self._client.get_accounts()

        for obj in accounts:
            account = obj.get_account()
            if account.props.provider_type == "lastfm":
                self._authentication = obj.get_oauth2_based()
                self._account = account
                self._music_disabled_id = self._account.connect(
                    'notify::music-disabled', self._goa_music_disabled)
                self._goa_music_disabled(self._account)
                break

    def _goa_music_disabled(self, klass, args=None):
        if self._account.props.music_disabled is True:
            self._state = GoaLastFM.State.DISABLED
        else:
            self._state = GoaLastFM.State.ENABLED

        self.notify("state")

    @GObject.Property(type=int, default=0, flags=GObject.ParamFlags.READABLE)
    def state(self):
        """Retrieve the state for the Last.fm account

        :returns: The account state
        :rtype: GoaLastFM.State
        """
        return self._state

    def enable_music(self):
        """Enable music suport of the Last.fm account"""
        self._account.props.music_disabled = False

    @GObject.Property(type=str, default="", flags=GObject.ParamFlags.READABLE)
    def identity(self):
        """Get Last.fm account identity

        :returns: Last.fm account identity
        :rtype: str
        """
        return self._account.props.identity

    @GObject.Property
    def secret(self):
        """Retrieve the Last.fm client secret"""
        return self._authentication.props.client_secret

    @GObject.Property
    def client_id(self):
        """Retrieve the Last.fm client id"""
        return self._authentication.props.client_id

    @GObject.Property
    def session_key(self):
        """Retrieve the Last.fm session key"""
        try:
            return self._authentication.call_get_access_token_sync(None)[0]
        except GLib.Error as e:
            self._log.warning(
                "Error: Unable to retrieve last.fm session key: {}".format(
                    e.message))
            return None

    def configure(self):
        if self.props.state == GoaLastFM.State.NOT_AVAILABLE:
            self._log.warning("Error, cannot configure a Last.fm account.")
            return

        Gio.bus_get(Gio.BusType.SESSION, None, self._get_connection_db, None)

    def _get_connection_db(self, source, res, user_data=None):
        try:
            connection = Gio.bus_get_finish(res)
        except GLib.Error as e:
            self._log.warning(
                "Error: Unable to get the DBus connection: {}".format(
                    e.message))
            return

        Gio.DBusProxy.new(connection, Gio.DBusProxyFlags.NONE, None,
                          "org.gnome.ControlCenter",
                          "/org/gnome/ControlCenter", "org.gtk.Actions", None,
                          self._get_control_center_proxy_cb, None)

    def _get_control_center_proxy_cb(self, source, res, user_data=None):
        try:
            settings_proxy = Gio.DBusProxy.new_finish(res)
        except GLib.Error as e:
            self._log.warning("Error: Unable to get a proxy: {}".format(
                e.message))
            return

        if self._state == GoaLastFM.State.NOT_CONFIGURED:
            params = [GLib.Variant("s", "add"), GLib.Variant("s", "lastfm")]
        else:
            params = [GLib.Variant("s", self._account.props.id)]

        args = GLib.Variant("(sav)", ("online-accounts", params))
        variant = GLib.Variant("(sava{sv})", ("launch-panel", [args], {}))
        settings_proxy.call("Activate", variant, Gio.DBusCallFlags.NONE, -1,
                            None, self._on_control_center_activated)

    def _on_control_center_activated(self, proxy, res, user_data=None):
        try:
            proxy.call_finish(res)
        except GLib.Error as e:
            self._log.warning(
                "Error: Failed to activate control-center: {}".format(
                    e.message))
class ArtCache(GObject.GObject):
    """Handles retrieval of MediaArt cache art

    Uses signals to indicate success or failure and always returns a
    Cairo.Surface.
    """

    __gtype_name__ = "ArtCache"

    __gsignals__ = {
        "result": (GObject.SignalFlags.RUN_FIRST, None, (object, ))
    }

    _log = MusicLogger()

    def __init__(self, size, scale):
        super().__init__()

        self._size = size
        self._scale = scale

        self._coreobject = None
        self._default_icon = None
        self._loading_icon = None

    def query(self, coreobject):
        """Start the cache query

        :param coreobject: The object to search art for
        """
        self._coreobject = coreobject

        if isinstance(coreobject, CoreArtist):
            self._loading_icon = DefaultIcon().get(
                DefaultIcon.Type.ARTIST_LOADING, self._size, self._scale, True)
            self._default_icon = DefaultIcon().get(DefaultIcon.Type.ARTIST,
                                                   self._size, self._scale,
                                                   True)
        elif (isinstance(coreobject, CoreAlbum)
              or isinstance(coreobject, CoreSong)):
            self._loading_icon = DefaultIcon().get(DefaultIcon.Type.LOADING,
                                                   self._size, self._scale)
            self._default_icon = DefaultIcon().get(DefaultIcon.Type.MUSIC,
                                                   self._size, self._scale)

        thumbnail_uri = coreobject.props.thumbnail
        if thumbnail_uri == "loading":
            self.emit("result", self._loading_icon)
            return
        elif thumbnail_uri == "generic":
            self.emit("result", self._default_icon)
            return

        thumb_file = Gio.File.new_for_uri(thumbnail_uri)
        if thumb_file:
            thumb_file.read_async(GLib.PRIORITY_LOW, None, self._open_stream,
                                  None)
            return

        self.emit("result", self._default_icon)

    def _open_stream(self, thumb_file, result, arguments):
        try:
            stream = thumb_file.read_finish(result)
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.domain,
                                                     error.message))
            self.emit("result", self._default_icon)
            return

        GdkPixbuf.Pixbuf.new_from_stream_async(stream, None,
                                               self._pixbuf_loaded, None)

    def _pixbuf_loaded(self, stream, result, data):
        try:
            pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish(result)
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.domain,
                                                     error.message))
            self.emit("result", self._default_icon)
            return

        stream.close_async(GLib.PRIORITY_LOW, None, self._close_stream, None)

        surface = Gdk.cairo_surface_create_from_pixbuf(pixbuf, self._scale,
                                                       None)
        if isinstance(self._coreobject, CoreArtist):
            surface = _make_icon_frame(surface,
                                       self._size,
                                       self._scale,
                                       round_shape=True)
        elif (isinstance(self._coreobject, CoreAlbum)
              or isinstance(self._coreobject, CoreSong)):
            surface = _make_icon_frame(surface, self._size, self._scale)

        self.emit("result", surface)

    def _close_stream(self, stream, result, data):
        try:
            stream.close_finish(result)
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.domain,
                                                     error.message))
Beispiel #9
0
class MediaKeys(GObject.GObject):
    """Media keys handling for Music
    """

    __gtype_name__ = 'MediaKeys'

    def __init__(self, player, window):
        """Initialize media keys handling

        :param Player player: Player object
        :param Gtk.Window window: Window to grab keys if focused
        """
        super().__init__()

        self._log = MusicLogger()

        self._player = player
        self._window = window

        self._media_keys_proxy = None

        self._init_media_keys_proxy()

    def _init_media_keys_proxy(self):
        def name_appeared(connection, name, name_owner, data=None):
            Gio.DBusProxy.new_for_bus(
                Gio.BusType.SESSION, Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES,
                None, "org.gnome.SettingsDaemon.MediaKeys",
                "/org/gnome/SettingsDaemon/MediaKeys",
                "org.gnome.SettingsDaemon.MediaKeys", None,
                self._media_keys_proxy_ready)

        Gio.bus_watch_name(Gio.BusType.SESSION,
                           "org.gnome.SettingsDaemon.MediaKeys",
                           Gio.BusNameWatcherFlags.NONE, name_appeared, None)

    def _media_keys_proxy_ready(self, proxy, result, data=None):
        try:
            self._media_keys_proxy = proxy.new_finish(result)
        except GLib.Error as e:
            self._log.warning("Error: Failed to contact settings daemon:",
                              e.message)
            return

        self._media_keys_proxy.connect("g-signal", self._handle_media_keys)

        self._ctrlr = Gtk.EventControllerKey().new(self._window)
        self._ctrlr.props.propagation_phase = Gtk.PropagationPhase.CAPTURE
        self._ctrlr.connect("focus-in", self._grab_media_player_keys)

    def _grab_media_player_keys(self, controllerkey=None):
        def proxy_call_finished(proxy, result, data=None):
            try:
                proxy.call_finish(result)
            except GLib.Error as e:
                self._log.warning(
                    "Error: Failed to grab mediaplayer keys: {}".format(
                        e.message))

        self._media_keys_proxy.call("GrabMediaPlayerKeys",
                                    GLib.Variant("(su)", ("Music", 0)),
                                    Gio.DBusCallFlags.NONE, -1, None,
                                    proxy_call_finished)

    def _handle_media_keys(self, proxy, sender, signal, parameters):
        app, response = parameters.unpack()
        if app != "Music":
            return

        if "Play" in response:
            self._player.play_pause()
        elif "Stop" in response:
            self._player.stop()
        elif "Next" in response:
            self._player.next()
        elif "Previous" in response:
            self._player.previous()
Beispiel #10
0
class PauseOnSuspend(GObject.GObject):
    """PauseOnSuspend object

    Contains logic to pause music on system suspend
    and inhibit suspend before pause.
    """
    def __init__(self, player):
        """Initialize pause on supend handling

        :param Player player: Player object
        """
        super().__init__()

        self._log = MusicLogger()

        self._player = player
        self._init_pause_on_suspend()

        self._connection = None
        self._file_descriptor = -1
        self._suspend_proxy = None
        self._previous_state = self._player.props.state
        self._player.connect("notify::state", self._on_player_state_changed)

    def _on_player_state_changed(self, klass, arguments):
        new_state = self._player.props.state
        if self._previous_state == new_state:
            return

        if (new_state == Playback.PLAYING and self._file_descriptor == -1):
            self._take_lock()

        if (self._previous_state == Playback.PLAYING
                and new_state != Playback.LOADING):
            self._release_lock()

        self._previous_state = new_state

    def _take_lock(self):
        variant = GLib.Variant(
            "(ssss)",
            ("sleep", "GNOME Music", "GNOME Music is pausing", "delay"))

        self._suspend_proxy.call("Inhibit", variant, Gio.DBusCallFlags.NONE,
                                 -1, None, self._on_inhibit)

    def _on_inhibit(self, proxy, task, data=None):
        if not self._suspend_proxy:
            return

        try:
            var = self._suspend_proxy.call_with_unix_fd_list_finish(task)
            self._file_descriptor = var.out_fd_list.get(0)
            self._connection = self._suspend_proxy.connect(
                "g-signal", self._pause_playing)
        except GLib.Error as e:
            self._log.warning("Error: Failed to finish proxy call: {}".format(
                e.message))

    def _release_lock(self):
        if self._file_descriptor >= 0:
            os.close(self._file_descriptor)
            self._file_descriptor = -1
            self._suspend_proxy.disconnect(self._connection)

    def _init_pause_on_suspend(self):
        Gio.DBusProxy.new_for_bus(Gio.BusType.SYSTEM,
                                  Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES,
                                  None, "org.freedesktop.login1",
                                  "/org/freedesktop/login1",
                                  "org.freedesktop.login1.Manager", None,
                                  self._suspend_proxy_ready)

    def _suspend_proxy_ready(self, proxy, result, data=None):
        try:
            self._suspend_proxy = proxy.new_finish(result)
        except GLib.Error as e:
            self._log.warning(
                "Error: Failed to contact logind daemon: {}".format(e.message))
            return

    def _pause_playing(self, proxy, sender, signal, parameters):
        if signal != "PrepareForSleep":
            return

        (going_to_sleep, ) = parameters
        if going_to_sleep is True:
            self._player.pause()
            self._release_lock()
        else:
            self._take_lock()
Beispiel #11
0
class StoreArt(GObject.Object):
    """Stores Art in the MediaArt cache.
    """
    def __init__(self, coreobject, uri):
        """Initialize StoreArtistArt

        :param coreobject: The CoreArtist or CoreAlbum to store art for
        :param string uri: The art uri
        """
        self._coreobject = coreobject

        self._log = MusicLogger()
        self._soup_session = Soup.Session.new()

        if (uri is None or uri == ""):
            self._coreobject.props.thumbnail = "generic"
            return

        cache_dir = GLib.build_filenamev(
            [GLib.get_user_cache_dir(), "media-art"])
        cache_dir_file = Gio.File.new_for_path(cache_dir)
        cache_dir_file.query_info_async(Gio.FILE_ATTRIBUTE_ACCESS_CAN_READ,
                                        Gio.FileQueryInfoFlags.NONE,
                                        GLib.PRIORITY_LOW, None,
                                        self._cache_dir_info_read, uri)

    def _cache_dir_info_read(self, cache_dir_file, res, uri):
        try:
            cache_dir_file.query_info_finish(res)
        except GLib.Error:
            # directory does not exist yet
            try:
                cache_dir_file.make_directory(None)
            except GLib.Error as error:
                self._log.warning("Error: {}, {}".format(
                    error.domain, error.message))
                self._coreobject.props.thumbnail = "generic"
                return

        msg = Soup.Message.new("GET", uri)
        self._soup_session.queue_message(msg, self._read_callback, None)

    def _read_callback(self, src, result, data):
        if result.props.status_code != 200:
            self._log.debug("Failed to get remote art: {}".format(
                result.props.reason_phrase))
            return

        try:
            [tmp_file, iostream] = Gio.File.new_tmp()
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.domain,
                                                     error.message))
            self._coreobject.props.thumbnail = "generic"
            return

        istream = Gio.MemoryInputStream.new_from_bytes(
            result.props.response_body_data)
        ostream = iostream.get_output_stream()
        # FIXME: Passing the iostream here, otherwise it gets
        # closed. PyGI specific issue?
        ostream.splice_async(
            istream, Gio.OutputStreamSpliceFlags.CLOSE_SOURCE
            | Gio.OutputStreamSpliceFlags.CLOSE_TARGET, GLib.PRIORITY_LOW,
            None, self._splice_callback, [tmp_file, iostream])

    def _delete_callback(self, src, result, data):
        try:
            src.delete_finish(result)
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.domain,
                                                     error.message))

    def _splice_callback(self, src, result, data):
        tmp_file, iostream = data

        iostream.close_async(GLib.PRIORITY_LOW, None,
                             self._close_iostream_callback, None)

        try:
            src.splice_finish(result)
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.domain,
                                                     error.message))
            self._coreobject.props.thumbnail = "generic"
            return

        if isinstance(self._coreobject, CoreArtist):
            success, cache_file = MediaArt.get_file(
                self._coreobject.props.artist, None, "artist")
        elif isinstance(self._coreobject, CoreAlbum):
            success, cache_file = MediaArt.get_file(
                self._coreobject.props.artist, self._coreobject.props.title,
                "album")
        elif isinstance(self._coreobject, CoreSong):
            success, cache_file = MediaArt.get_file(
                self._coreobject.props.artist, self._coreobject.props.album,
                "album")
        else:
            success = False

        if not success:
            self._coreobject.props.thumbnail = "generic"
            return

        try:
            # FIXME: I/O blocking
            MediaArt.file_to_jpeg(tmp_file.get_path(), cache_file.get_path())
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.domain,
                                                     error.message))
            self._coreobject.props.thumbnail = "generic"
            return

        self._coreobject.props.media.set_thumbnail(cache_file.get_uri())
        self._coreobject.props.thumbnail = cache_file.get_uri()

        tmp_file.delete_async(GLib.PRIORITY_LOW, None, self._delete_callback,
                              None)

    def _close_iostream_callback(self, src, result, data):
        try:
            src.close_finish(result)
        except GLib.Error as error:
            self._log.warning("Error: {}, {}".format(error.domain,
                                                     error.message))
Beispiel #12
0
class Application(Gtk.Application):
    def __init__(self, application_id):
        super().__init__(application_id=application_id,
                         flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.props.resource_base_path = "/org/gnome/Music"
        GLib.set_application_name(_("Music"))
        GLib.set_prgname(application_id)
        GLib.setenv("PULSE_PROP_media.role", "music", True)

        self._init_style()
        self._window = None

        self._log = MusicLogger()
        self._search = Search()

        self._notificationmanager = NotificationManager(self)
        self._coreselection = CoreSelection()
        self._coremodel = CoreModel(self)
        # Order is important: CoreGrilo initializes the Grilo sources,
        # which in turn use CoreModel & CoreSelection extensively.
        self._coregrilo = CoreGrilo(self)

        self._settings = Gio.Settings.new('org.gnome.Music')
        self._lastfm_scrobbler = LastFmScrobbler(self)
        self._player = Player(self)

        InhibitSuspend(self)
        PauseOnSuspend(self._player)

    def _init_style(self):
        css_provider = Gtk.CssProvider()
        css_provider.load_from_resource('/org/gnome/Music/org.gnome.Music.css')
        screen = Gdk.Screen.get_default()
        style_context = Gtk.StyleContext()
        style_context.add_provider_for_screen(
            screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)

    @GObject.Property(type=CoreGrilo,
                      default=None,
                      flags=GObject.ParamFlags.READABLE)
    def coregrilo(self):
        """Get application-wide CoreGrilo instance.

        :returns: The grilo wrapper
        :rtype: CoreGrilo
        """
        return self._coregrilo

    @GObject.Property(type=MusicLogger,
                      default=None,
                      flags=GObject.ParamFlags.READABLE)
    def log(self):
        """Get application-wide logging facility.

        :returns: the logger
        :rtype: MusicLogger
        """
        return self._log

    @GObject.Property(type=Player,
                      default=None,
                      flags=GObject.ParamFlags.READABLE)
    def player(self):
        """Get application-wide music player.

        :returns: the player
        :rtype: Player
        """
        return self._player

    @GObject.Property(type=Gio.Settings, flags=GObject.ParamFlags.READABLE)
    def settings(self):
        """Get application-wide settings.

        :returns: settings
        :rtype: Gio.settings
        """
        return self._settings

    @GObject.Property(type=CoreModel, flags=GObject.ParamFlags.READABLE)
    def coremodel(self):
        """Get class providing all listmodels.

        :returns: List model provider class
        :rtype: CoreModel
        """
        return self._coremodel

    @GObject.Property(type=CoreSelection, flags=GObject.ParamFlags.READABLE)
    def coreselection(self):
        """Get selection object.

        :returns: Object containing all selection info
        :rtype: CoreSelection
        """
        return self._coreselection

    @GObject.Property(type=LastFmScrobbler, flags=GObject.ParamFlags.READABLE)
    def lastfm_scrobbler(self):
        """Get Last.fm scrobbler.

        :returns: Last.fm scrobbler
        :rtype: LastFmScrobbler
        """
        return self._lastfm_scrobbler

    @GObject.Property(type=Window, flags=GObject.ParamFlags.READABLE)
    def window(self):
        """Get main window.

        :returns: Main window.
        :rtype: Window
        """
        return self._window

    @GObject.Property(type=Search, flags=GObject.ParamFlags.READABLE)
    def search(self):
        """Get class providing search logic.

        :returns: List model provider class
        :rtype: Search
        """
        return self._search

    @GObject.Property(type=NotificationManager,
                      flags=GObject.ParamFlags.READABLE)
    def notificationmanager(self):
        """Get notification manager

        :returns: notification manager
        :rtype: NotificationManager
        """
        return self._notificationmanager

    def _set_actions(self):
        action_entries = [('about', self._about, None),
                          ("help", self._help, ("app.help", ["F1"])),
                          ("lastfm-configure", self._lastfm_account, None),
                          ("quit", self._quit, ("app.quit", ["<Ctrl>Q"]))]

        for action, callback, accel in action_entries:
            simple_action = Gio.SimpleAction.new(action, None)
            simple_action.connect('activate', callback)
            self.add_action(simple_action)
            if accel is not None:
                self.set_accels_for_action(*accel)

    def _help(self, action, param):
        try:
            Gtk.show_uri(None, "help:gnome-music", Gdk.CURRENT_TIME)
        except GLib.Error:
            self._log.message("Help handler not available.")

    def _lastfm_account(self, action, param):
        lastfm_dialog = LastfmDialog(self._window, self._lastfm_scrobbler)
        lastfm_dialog.run()
        lastfm_dialog.destroy()

    def _about(self, action, param):
        about = AboutDialog()
        about.props.transient_for = self._window
        about.present()

    def do_startup(self):
        Gtk.Application.do_startup(self)
        self._set_actions()

    def _quit(self, action=None, param=None):
        self._window.destroy()

    def do_activate(self):
        if not self._window:
            self._window = Window(self)
            self.notify("window")
            self._window.set_default_icon_name(self.props.application_id)
            if self.props.application_id == "org.gnome.Music.Devel":
                self._window.get_style_context().add_class('devel')
            MPRIS(self)

        self._window.present()