コード例 #1
0
ファイル: window.py プロジェクト: erwanj/lollypop
class Window(Gtk.ApplicationWindow):

	"""
		Init window objects
	"""	
	def __init__(self, app):
		Gtk.ApplicationWindow.__init__(self,
					       application=app,
					       title=_("Lollypop"))

		self._timeout = None
		self._scanner = CollectionScanner()
		self._scanner.connect("scan-finished", self._setup_list_one, True)

		self._setup_window()				
		self._setup_view()

		self._setup_media_keys()

		party_settings = Objects["settings"].get_value('party-ids')
		ids = []
		for setting in party_settings:
			if isinstance(setting, int):
				ids.append(setting)	
		Objects["player"].set_party_ids(ids)
		self.connect("destroy", self._on_destroyed_window)

	"""
		Run collection update if needed
	"""	
	def setup_view(self):
		if Objects["tracks"].is_empty():
			self._scanner.update(self._progress, False)
			return
		elif Objects["settings"].get_value('startup-scan'):
			self._scanner.update(self._progress, True)
			
		self._setup_list_one()
		self._update_view_albums(POPULARS)

	"""
		Update music database
	"""
	def update_db(self):
		self._list_one.widget.hide()
		self._list_two.widget.hide()

		old_view = self._stack.get_visible_child()		
		self._loading = True
		view = LoadingView()
		self._stack.add(view)
		self._stack.set_visible_child(view)
		self._scanner.update(self._progress, False)
		if old_view:
			self._stack.remove(old_view)
			old_view.remove_signals()


	"""
		Update view class
		@param bool
	"""
	def update_view_class(self, dark):
		current_view = self._stack.get_visible_child()
		if dark:
			current_view.get_style_context().add_class('black')
		else:
			current_view.get_style_context().remove_class('black')

############
# Private  #
############

	"""
		Setup media player keys
	"""
	def _setup_media_keys(self):
		self._proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None),
											 Gio.DBusProxyFlags.NONE,
											 None,
											 'org.gnome.SettingsDaemon',
											 '/org/gnome/SettingsDaemon/MediaKeys',
											 'org.gnome.SettingsDaemon.MediaKeys',
											 None)
		self._grab_media_player_keys()
		try:
			self._proxy.connect('g-signal', self._handle_media_keys)
		except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
			pass

	"""
		Do key grabbing
	"""
	def _grab_media_player_keys(self):
		try:
			self._proxy.call_sync('GrabMediaPlayerKeys',
								 GLib.Variant('(su)', ('Lollypop', 0)),
								 Gio.DBusCallFlags.NONE,
								 -1,
								 None)
		except GLib.GError:
			# We cannot grab media keys if no settings daemon is running
			pass

	"""
		Do player actions in response to media key pressed
	"""
	def _handle_media_keys(self, proxy, sender, signal, parameters):
		if signal != 'MediaPlayerKeyPressed':
			print('Received an unexpected signal \'%s\' from media player'.format(signal))
			return
		response = parameters.get_child_value(1).get_string()
		if 'Play' in response:
			Objects["player"].play_pause()
		elif 'Stop' in response:
			Objects["player"].stop()
		elif 'Next' in response:
			Objects["player"].next()
		elif 'Previous' in response:
			Objects["player"].prev()
	
	"""
		Setup window icon, position and size, callback for updating this values
	"""
	def _setup_window(self):
		self.set_icon_name('lollypop')
		size_setting = Objects["settings"].get_value('window-size')
		if isinstance(size_setting[0], int) and isinstance(size_setting[1], int):
			self.resize(size_setting[0], size_setting[1])
		else:
			self.set_size_request(800, 600)
		position_setting = Objects["settings"].get_value('window-position')
		if len(position_setting) == 2 \
			and isinstance(position_setting[0], int) \
			and isinstance(position_setting[1], int):
			self.move(position_setting[0], position_setting[1])

		if Objects["settings"].get_value('window-maximized'):
			self.maximize()

		self.connect("window-state-event", self._on_window_state_event)
		self.connect("configure-event", self._on_configure_event)

	"""
		Setup window main view:
			- genre list
			- artist list
			- main view as artist view or album view
	"""
	def _setup_view(self):
		self._paned_main_list = Gtk.HPaned()
		self._paned_list_view = Gtk.HPaned()
		vgrid = Gtk.Grid()
		vgrid.set_orientation(Gtk.Orientation.VERTICAL)
	
		self._toolbar = Toolbar()
		self._toolbar.header_bar.show()
		self._toolbar.get_view_genres_btn().connect("toggled", self._setup_list_one)

		self._list_one = SelectionList("Genre")
		self._list_two = SelectionList("Artist")
		self._list_one_signal = None
		self._list_two_signal = None
		
		self._loading = True
		loading_view = LoadingView()

		self._stack = Gtk.Stack()
		self._stack.add(loading_view)
		self._stack.set_visible_child(loading_view)
		self._stack.set_transition_duration(500)
		self._stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
		self._stack.show()

		self._progress = Gtk.ProgressBar()

		vgrid.add(self._stack)
		vgrid.add(self._progress)
		vgrid.show()

		DESKTOP = environ.get("XDG_CURRENT_DESKTOP")
		if DESKTOP and ("GNOME" in DESKTOP or "Pantheon" in DESKTOP):
			self.set_titlebar(self._toolbar.header_bar)
			self._toolbar.header_bar.set_show_close_button(True)
			self.add(self._paned_main_list)
		else:
			hgrid = Gtk.Grid()
			hgrid.set_orientation(Gtk.Orientation.VERTICAL)
			hgrid.add(self._toolbar.header_bar)
			hgrid.add(self._paned_main_list)
			hgrid.show()
			self.add(hgrid)

		separator = Gtk.Separator()
		separator.show()
		self._paned_list_view.add1(self._list_two.widget)
		self._paned_list_view.add2(vgrid)
		self._paned_main_list.add1(self._list_one.widget)
		self._paned_main_list.add2(self._paned_list_view)
		self._paned_main_list.set_position(Objects["settings"].get_value("paned-mainlist-width").get_int32())
		self._paned_list_view.set_position(Objects["settings"].get_value("paned-listview-width").get_int32())
		self._paned_main_list.show()
		self._paned_list_view.show()
		self.show()

	"""
		Init the filter list
		@param widget as unused
	"""
	def _init_main_list(self, widget):
		if self._list_one.widget.is_visible():
			self._update_genres()
		else:
			self._init_genres()
	"""
		Init list with genres or artist
		If update, only update list content
		@param obj as unused, bool
	"""
	def _setup_list_one(self, obj = None, update = None):
		if self._list_one_signal:
			self._list_one.disconnect(self._list_one_signal)
		active = self._toolbar.get_view_genres_btn().get_active()
		if active:
			items = Objects["genres"].get_ids()
		else:
			self._list_two.widget.hide()
			items = Objects["artists"].get_ids(ALL)
			if len(Objects["albums"].get_compilations(ALL)) > 0:
				items.insert(0, (COMPILATIONS, _("Compilations")))

		items.insert(0, (ALL, _("All artists")))
		items.insert(0, (POPULARS, _("Popular albums")))

		if update:
			self._list_one.update(items, not active)
		else:
			self._list_one.populate(items, not active)		

		# If was empty
		if not self._list_one_signal:
			self._list_one.select_first()

		if self._loading:
			self._stack.get_visible_child().hide()
			self._list_one.select_first()
			self._update_view_albums(POPULARS)
			self._loading = False

		self._list_one.widget.show()
		if active:
			self._list_one_signal = self._list_one.connect('item-selected', self._setup_list_two)
		else:
			self._list_one_signal = self._list_one.connect('item-selected', self._update_view_artists, None)

	"""
		Init list two with artist based on genre
		@param obj as unused, genre id as int
	"""
	def _setup_list_two(self, obj, genre_id):
		if self._list_two_signal:
			self._list_two.disconnect(self._list_two_signal)

		if genre_id == POPULARS:
			self._list_two.widget.hide()
			self._list_two_signal = None
		else:
			
			values = Objects["artists"].get_ids(genre_id)
			if len(Objects["albums"].get_compilations(genre_id)) > 0:
				values.insert(0, (COMPILATIONS, _("Compilations")))
			self._list_two.populate(values, True)
			self._list_two.widget.show()
			self._list_two_signal = self._list_two.connect('item-selected', self._update_view_artists, genre_id)
		self._update_view_albums(genre_id)
		
	"""
		Update artist view
		@param artist id as int, genre id as int
	"""
	def _update_view_artists(self, obj, artist_id, genre_id):
		if artist_id == ALL or artist_id == POPULARS:
			self._update_view_albums(artist_id)
		else:
			old_view = self._stack.get_visible_child()
			view = ArtistView(artist_id, genre_id, False)
			self._stack.add(view)
			start_new_thread(view.populate, ())
			self._stack.set_visible_child(view)
			if old_view:
				self._stack.remove(old_view)
				old_view.remove_signals()

	"""
		Update albums view
		@param genre id as int
	"""
	def _update_view_albums(self, genre_id):
		old_view = self._stack.get_visible_child()
		view = AlbumView(genre_id)
		self._stack.add(view)
		start_new_thread(view.populate, ())
		self._stack.set_visible_child(view)
		if old_view:
			self._stack.remove(old_view)
			old_view.remove_signals()

	"""
		Delay event
		@param: widget as Gtk.Window
		@param: event as Gtk.Event
	"""		
	def _on_configure_event(self, widget, event):
		if self._timeout:
			GLib.source_remove(self._timeout)
		self._timeout = GLib.timeout_add(500, self._save_size_position, widget)
	
	"""
		Save window state, update current view content size
		@param: widget as Gtk.Window
	"""
	def _save_size_position(self, widget):
		self._timeout = None
		size = widget.get_size()
		Objects["settings"].set_value('window-size', GLib.Variant('ai', [size[0], size[1]]))
		position = widget.get_position()
		Objects["settings"].set_value('window-position', GLib.Variant('ai', [position[0], position[1]]))

	"""
		Save maximised state
	"""
	def _on_window_state_event(self, widget, event):
		Objects["settings"].set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names)

	"""
		Save paned widget width
		@param widget as unused, data as unused
	"""	
	def _on_destroyed_window(self, widget):
		Objects["settings"].set_value("paned-mainlist-width", GLib.Variant('i', self._paned_main_list.get_position()))
		Objects["settings"].set_value("paned-listview-width", GLib.Variant('i', self._paned_list_view.get_position()))
コード例 #2
0
class Application(Gtk.Application, ApplicationActions):
    """
        Lollypop application:
            - Handle appmenu
            - Handle command line
            - Create main window
    """
    def __init__(self, version):
        """
            Create application
            @param version as str
        """
        Gtk.Application.__init__(
            self,
            application_id="org.gnome.Lollypop",
            flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
        self.__version = version
        self.set_property("register-session", True)
        signal(SIGINT, lambda a, b: self.quit())
        signal(SIGTERM, lambda a, b: self.quit())
        # Set main thread name
        # We force it to current python 3.6 name, to be sure in case of
        # change in python
        current_thread().setName("MainThread")
        set_proxy_from_gnome()
        GLib.setenv("PULSE_PROP_media.role", "music", True)
        GLib.setenv("PULSE_PROP_application.icon_name", "org.gnome.Lollypop",
                    True)
        # Fix proxy for python
        proxy = GLib.environ_getenv(GLib.get_environ(), "all_proxy")
        if proxy is not None and proxy.startswith("socks://"):
            proxy = proxy.replace("socks://", "socks4://")
            from os import environ
            environ["all_proxy"] = proxy
            environ["ALL_PROXY"] = proxy
        # Ideally, we will be able to delete this once Flatpak has a solution
        # for SSL certificate management inside of applications.
        if GLib.file_test("/app", GLib.FileTest.EXISTS):
            paths = [
                "/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/cert.pem",
                "/etc/ssl/cert.pem"
            ]
            for path in paths:
                if GLib.file_test(path, GLib.FileTest.EXISTS):
                    GLib.setenv("SSL_CERT_FILE", path, True)
                    break
        self.cursors = {}
        self.notify = None
        self.scrobblers = []
        self.debug = False
        self.shown_sidebar_tooltip = False
        self.__window = None
        self.__fs_window = None
        self.__scanner_timeout_id = None
        self.__scanner_uris = []
        GLib.set_application_name("Lollypop")
        GLib.set_prgname("lollypop")
        self.add_main_option("play-ids", b"a", GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING, "Play ids", None)
        self.add_main_option("debug", b"d", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Debug Lollypop", None)
        self.add_main_option("set-rating", b"r", GLib.OptionFlags.NONE,
                             GLib.OptionArg.STRING, "Rate the current track",
                             None)
        self.add_main_option("play-pause", b"t", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Toggle playback", None)
        self.add_main_option("stop", b"s", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Stop playback", None)
        self.add_main_option("next", b"n", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Go to next track", None)
        self.add_main_option("prev", b"p", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Go to prev track", None)
        ## anhsirk0 edits
        self.add_main_option("set-next", b"m", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Set next track by track id",
                             None)
        ## anhsirk0 edits ends

        self.add_main_option("emulate-phone", b"e", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Emulate a Librem phone",
                             None)
        self.add_main_option("version", b"v", GLib.OptionFlags.NONE,
                             GLib.OptionArg.NONE, "Lollypop version", None)
        self.connect("command-line", self.__on_command_line)
        self.connect("handle-local-options", self.__on_handle_local_options)
        self.connect("activate", self.__on_activate)
        self.connect("shutdown", lambda a: self.__save_state())
        self.register(None)
        if self.get_is_remote():
            Gdk.notify_startup_complete()

    def init(self):
        """
            Init main application
        """
        self.settings = Settings.new()
        # Mount enclosing volume as soon as possible
        uris = self.settings.get_music_uris()
        try:
            for uri in uris:
                if uri.startswith("file:/"):
                    continue
                f = Gio.File.new_for_uri(uri)
                f.mount_enclosing_volume(Gio.MountMountFlags.NONE, None, None,
                                         None)
        except Exception as e:
            Logger.error("Application::init(): %s" % e)

        cssProviderFile = Gio.File.new_for_uri(
            "resource:///org/gnome/Lollypop/application.css")
        cssProvider = Gtk.CssProvider()
        cssProvider.load_from_file(cssProviderFile)
        screen = Gdk.Screen.get_default()
        styleContext = Gtk.StyleContext()
        styleContext.add_provider_for_screen(screen, cssProvider,
                                             Gtk.STYLE_PROVIDER_PRIORITY_USER)
        self.db = Database()
        self.playlists = Playlists()
        self.albums = AlbumsDatabase()
        self.artists = ArtistsDatabase()
        self.genres = GenresDatabase()
        self.tracks = TracksDatabase()
        self.player = Player()
        self.inhibitor = Inhibitor()
        self.scanner = CollectionScanner()
        self.art = Art()
        self.notify = NotificationManager()
        self.art.update_art_size()
        self.task_helper = TaskHelper()
        self.art_helper = ArtHelper()
        self.spotify = SpotifyHelper()
        if not self.settings.get_value("disable-mpris"):
            from lollypop.mpris import MPRIS
            MPRIS(self)

        settings = Gtk.Settings.get_default()
        self.__gtk_dark = settings.get_property(
            "gtk-application-prefer-dark-theme")
        if not self.__gtk_dark:
            dark = self.settings.get_value("dark-ui")
            settings.set_property("gtk-application-prefer-dark-theme", dark)
        ApplicationActions.__init__(self)
        startup_one_ids = self.settings.get_value("startup-one-ids")
        startup_two_ids = self.settings.get_value("startup-two-ids")
        if startup_one_ids:
            self.settings.set_value("state-one-ids", startup_one_ids)
            self.settings.set_value("state-three-ids", GLib.Variant("ai", []))
        if startup_two_ids:
            self.settings.set_value("state-two-ids", startup_two_ids)

    def do_startup(self):
        """
            Init application
        """
        Gtk.Application.do_startup(self)

        if self.__window is None:
            self.init()
            self.__window = Window()
            self.__window.connect("delete-event", self.__hide_on_delete)
            self.__window.show()
            self.player.restore_state()

    def quit(self, vacuum=False):
        """
            Quit Lollypop
            @param vacuum as bool
        """
        if self.settings.get_value("save-state"):
            # Special case, we can't handle this earlier
            if self.window.is_adaptive:
                visible = self.window.container.stack.get_visible_child()
                if visible == self.window.container.list_one:
                    self.settings.set_value("state-one-ids",
                                            GLib.Variant("ai", []))
                    self.settings.set_value("state-two-ids",
                                            GLib.Variant("ai", []))
                elif visible == self.window.container.list_two:
                    selected_ids = self.window.container.list_one.selected_ids
                    self.settings.set_value("state-one-ids",
                                            GLib.Variant("ai", selected_ids))
                    self.settings.set_value("state-two-ids",
                                            GLib.Variant("ai", []))
        else:
            self.settings.set_value("state-one-ids", GLib.Variant("ai", []))
            self.settings.set_value("state-two-ids", GLib.Variant("ai", []))
        # Then vacuum db
        if vacuum:
            self.__vacuum()
            self.art.clean_web()
        self.__window.hide()
        for scrobbler in self.scrobblers:
            scrobbler.save()
        Gio.Application.quit(self)

    def set_mini(self):
        """
            Set mini player on/off
        """
        if self.__window is not None:
            self.__window.set_mini()

    def load_listenbrainz(self):
        """
            Load listenbrainz support if needed
        """
        if self.settings.get_value("listenbrainz-user-token").get_string():
            from lollypop.listenbrainz import ListenBrainz
            for scrobbler in self.scrobblers:
                if isinstance(scrobbler, ListenBrainz):
                    return
            listenbrainz = ListenBrainz()
            self.scrobblers.append(listenbrainz)
            self.settings.bind("listenbrainz-user-token", listenbrainz,
                               "user_token", 0)

    def fullscreen(self):
        """
            Go fullscreen
        """
        def on_destroy(window):
            self.__fs_window = None
            self.__window.show()

        if self.__fs_window is None:
            self.__window.hide()
            from lollypop.fullscreen import FullScreen
            self.__fs_window = FullScreen(self)
            self.__fs_window.show()
            self.__fs_window.connect("destroy", on_destroy)

    @property
    def devices(self):
        """
            Get available devices
            Merge connected and known
            @return [str]
        """
        devices = self.__window.toolbar.end.devices_popover.devices
        devices += list(self.settings.get_value("devices"))
        result = []
        # Do not use set() + filter() because we want to keep order
        for device in devices:
            if device not in result and device != "":
                result.append(device)
        return result

    @property
    def is_fullscreen(self):
        """
            Return True if application is fullscreen
        """
        return self.__fs_window is not None

    @property
    def lastfm(self):
        """
            Get lastfm provider from scrobbler
            @return LastFM/None
        """
        if LastFM is None:
            return None
        from pylast import LastFMNetwork
        for scrobbler in self.scrobblers:
            if isinstance(scrobbler, LastFMNetwork):
                return scrobbler
        return None

    @property
    def main_window(self):
        """
            Get main window
        """
        return self.__window

    @property
    def window(self):
        """
            Get current application window
            @return Gtk.Window
        """
        if self.__fs_window is not None:
            return self.__fs_window
        else:
            return self.__window

    @property
    def gtk_application_prefer_dark_theme(self):
        """
            Return default gtk value
            @return bool
        """
        return self.__gtk_dark

#######################
# PRIVATE             #
#######################

    def __save_state(self):
        """
            Save window position and view
        """
        if not self.settings.get_value("save-state"):
            return

        if self.player.current_track.id is None or\
                self.player.current_track.mtime == 0:
            track_id = None
        elif self.player.current_track.id == Type.RADIOS:
            from lollypop.radios import Radios
            radios = Radios()
            track_id = radios.get_id(self.player.current_track.radio_name)
        else:
            track_id = self.player.current_track.id
            # Save albums context
            try:
                with open(LOLLYPOP_DATA_PATH + "/Albums.bin", "wb") as f:
                    dump(list(self.player.albums), f)
            except Exception as e:
                Logger.error("Application::__save_state(): %s" % e)
        dump(track_id, open(LOLLYPOP_DATA_PATH + "/track_id.bin", "wb"))
        dump([self.player.is_playing, self.player.is_party],
             open(LOLLYPOP_DATA_PATH + "/player.bin", "wb"))
        dump(self.player.queue, open(LOLLYPOP_DATA_PATH + "/queue.bin", "wb"))
        # Save current playlist
        if self.player.current_track.id == Type.RADIOS:
            playlist_ids = [Type.RADIOS]
        elif not self.player.playlist_ids:
            playlist_ids = []
        else:
            playlist_ids = self.player.playlist_ids
        dump(playlist_ids, open(LOLLYPOP_DATA_PATH + "/playlist_ids.bin",
                                "wb"))
        if self.player.current_track.id is not None:
            position = self.player.position
        else:
            position = 0
        dump(position, open(LOLLYPOP_DATA_PATH + "/position.bin", "wb"))
        self.player.stop_all()
        self.__window.container.stop_all()

    def __vacuum(self):
        """
            VACUUM DB
        """
        try:
            if self.scanner.is_locked():
                self.scanner.stop()
                GLib.idle_add(self.__vacuum)
                return
            self.tracks.del_non_persistent()
            self.tracks.clean()
            self.albums.clean()
            self.artists.clean()
            self.genres.clean()

            from lollypop.radios import Radios
            with SqlCursor(self.db) as sql:
                sql.isolation_level = None
                sql.execute("VACUUM")
                sql.isolation_level = ""
            with SqlCursor(self.playlists) as sql:
                sql.isolation_level = None
                sql.execute("VACUUM")
                sql.isolation_level = ""
            with SqlCursor(Radios()) as sql:
                sql.isolation_level = None
                sql.execute("VACUUM")
                sql.isolation_level = ""
        except Exception as e:
            Logger.error("Application::__vacuum(): %s" % e)

    def __on_handle_local_options(self, app, options):
        """
            Handle local options
            @param app as Gio.Application
            @param options as GLib.VariantDict
        """
        if options.contains("version"):
            print("Lollypop %s" % self.__version)
            exit(0)
        return -1

    def __on_command_line(self, app, app_cmd_line):
        """
            Handle command line
            @param app as Gio.Application
            @param options as Gio.ApplicationCommandLine
        """
        try:
            args = app_cmd_line.get_arguments()
            options = app_cmd_line.get_options_dict()
            if options.contains("debug"):
                self.debug = True
            # We are forced to enable scrobblers here if we want full debug
            if not self.scrobblers:
                if LastFM is not None:
                    self.scrobblers = [LastFM("lastfm"), LastFM("librefm")]
                self.load_listenbrainz()
            if options.contains("set-rating"):
                value = options.lookup_value("set-rating").get_string()
                try:
                    value = min(max(0, int(value)), 5)
                    if self.player.current_track.id is not None:
                        self.player.current_track.set_rate(value)
                except Exception as e:
                    Logger.error("Application::__on_command_line(): %s", e)
                    pass
            elif options.contains("play-pause"):
                self.player.play_pause()
            elif options.contains("stop"):
                self.player.stop()
            ## anhsirk0 edits
            elif options.contains("set-next"):
                try:
                    track_id = int(args[1])
                    try:
                        self.player.append_to_queue(track_id, notify=True)
                    except:
                        pass
                except:
                    pass

            ## anhsirk0 edits ends

            elif options.contains("play-ids"):
                try:
                    value = options.lookup_value("play-ids").get_string()
                    ids = value.split(";")
                    tracks = []
                    for id in ids:
                        if id[0:2] == "a:":
                            album = Album(int(id[2:]))
                            tracks += album.tracks
                        else:
                            tracks.append(Track(int(id[2:])))
                    self.player.load(tracks[0])
                    self.player.populate_playlist_by_tracks(
                        tracks, [Type.SEARCH])
                except Exception as e:
                    Logger.error("Application::__on_command_line(): %s", e)
                    pass
            elif options.contains("next"):
                self.player.next()
            elif options.contains("prev"):
                self.player.prev()
            elif options.contains("emulate-phone"):
                self.__window.toolbar.end.devices_popover.add_fake_phone()
            elif len(args) > 1:
                uris = []
                pls = []
                for uri in args[1:]:
                    try:
                        uri = GLib.filename_to_uri(uri)
                    except:
                        pass
                    f = Gio.File.new_for_uri(uri)
                    if not f.query_exists():
                        uri = GLib.filename_to_uri(
                            "%s/%s" % (GLib.get_current_dir(), uri))
                        f = Gio.File.new_for_uri(uri)
                    if is_audio(f):
                        uris.append(uri)
                    elif is_pls(f):
                        pls.append(uri)
                    else:
                        info = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE,
                                            Gio.FileQueryInfoFlags.NONE, None)
                        if info.get_file_type() == Gio.FileType.DIRECTORY:
                            uris.append(uri)
                if pls:
                    from gi.repository import TotemPlParser
                    parser = TotemPlParser.Parser.new()
                    parser.connect("entry-parsed", self.__on_entry_parsed,
                                   uris)
                    parser.parse_async(uri, True, None,
                                       self.__on_parse_finished, uris)
                else:
                    self.__on_parse_finished(None, None, uris)
            elif self.__window is not None:
                if not self.__window.is_visible():
                    self.__window.present()
                    self.player.emit("status-changed")
                    self.player.emit("current-changed")
            Gdk.notify_startup_complete()
        except Exception as e:
            Logger.error("Application::__on_command_line(): %s", e)
        return 0

    def __on_parse_finished(self, parser, result, uris):
        """
            Play stream
            @param parser as TotemPlParser.Parser
            @param result as Gio.AsyncResult
            @param uris as [str]
        """
        def scanner_update():
            self.__scanner_timeout_id = None
            self.player.play_uris(self.__scanner_uris)
            self.scanner.update(ScanType.EPHEMERAL, self.__scanner_uris)
            self.__scanner_uris = []

        if self.__scanner_timeout_id is not None:
            GLib.source_remove(self.__scanner_timeout_id)
        self.__scanner_uris += uris
        self.__scanner_timeout_id = GLib.timeout_add(500, scanner_update)

    def __on_entry_parsed(self, parser, uri, metadata, uris):
        """
            Add playlist entry to external files
            @param parser as TotemPlParser.Parser
            @param uri as str
            @param metadata as GLib.HastTable
            @param uris as str
        """
        uris.append(uri)

    def __hide_on_delete(self, widget, event):
        """
            Hide window
            @param widget as Gtk.Widget
            @param event as Gdk.Event
        """
        # Quit if background mode is on but player is off
        if not self.settings.get_value("background-mode") or\
                not self.player.is_playing:
            GLib.idle_add(self.quit, True)
        return widget.hide_on_delete()

    def __on_activate(self, application):
        """
            Call default handler
            @param application as Gio.Application
        """
        self.__window.present()
コード例 #3
0
ファイル: window.py プロジェクト: aseempatni/lollypop
class Window(Gtk.ApplicationWindow):

	"""
		Init window objects
	"""
	def __init__(self, app, db, player):
		Gtk.ApplicationWindow.__init__(self,
					       application=app,
					       title=_("Lollypop"))
		
		self._db = db
		self._player = player
		self._scanner = CollectionScanner()
		self._settings = Gio.Settings.new('org.gnome.Lollypop')

		self._artist_signal_id = 0

		self._setup_window()				
		self._setup_view()
		self._setup_media_keys()

		party_settings = self._settings.get_value('party-ids')
		ids = []
		for setting in party_settings:
			if isinstance(setting, int):
				ids.append(setting)	
		self._player.set_party_ids(ids)
		
		self.connect("map-event", self._on_mapped_window)



	def edit_party(self):
		builder = Gtk.Builder()
		builder.add_from_resource('/org/gnome/Lollypop/PartyDialog.ui')
		self._party_dialog = builder.get_object('party_dialog')
		self._party_dialog.set_transient_for(self)
		self._party_dialog.set_title(_("Select what will be available in party mode"))
		party_button = builder.get_object('button1')
		party_button.connect("clicked", self._edit_party_close)
		scrolled = builder.get_object('scrolledwindow1')
		genres = self._db.get_all_genres()
		genres.insert(0, (-1, "Populars"))
		self._party_grid = Gtk.Grid()
		self._party_grid.set_orientation(Gtk.Orientation.VERTICAL)
		self._party_grid.set_property("column-spacing", 10)
		ids = self._player.get_party_ids()
		i = 0
		x = 0
		for genre_id, genre in genres:
			label = Gtk.Label()
			label.set_text(genre)
			switch = Gtk.Switch()
			if genre_id in ids:
				switch.set_state(True)
			switch.connect("state-set", self._party_switch_state, genre_id)
			self._party_grid.attach(label, x, i, 1, 1)
			self._party_grid.attach(switch, x+1, i, 1, 1)
			if x == 0:
				x += 2
			else:
				i += 1
				x = 0
		scrolled.add(self._party_grid)
		self._party_dialog.show_all()

	"""
		Update music database
		Empty database if reinit True
	"""
	def update_db(self, reinit):
		if reinit:
			self._player.stop()
			self._player.clear_albums()
			self._toolbar.update_toolbar(None, None)
			self._db.reset()
		self._list_genres.widget.hide()
		self._list_artists.widget.hide()
		self._box.remove(self._view)
		self._view = LoadingView()
		self._box.add(self._view)
		self._scanner.update(self._update_genres)

############
# Private  #
############

	"""
		Update party ids when use change a switch in dialog
	"""
	def _party_switch_state(self, widget, state, genre_id):
		ids = self._player.get_party_ids()
		if state:
			try:
				ids.append(genre_id)
			except:
				pass
		else:
			try:
				ids.remove(genre_id)
			except:
				pass
		self._player.set_party_ids(ids)
		self._settings.set_value('party-ids',  GLib.Variant('ai', ids))
		

	"""
		Close edit party dialog
	"""
	def _edit_party_close(self, widget):
		self._party_dialog.hide()
		self._party_dialog.destroy()

	"""
		Setup media player keys
	"""
	def _setup_media_keys(self):
		self._proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None),
											 Gio.DBusProxyFlags.NONE,
											 None,
											 'org.gnome.SettingsDaemon',
											 '/org/gnome/SettingsDaemon/MediaKeys',
											 'org.gnome.SettingsDaemon.MediaKeys',
											 None)
		self._grab_media_player_keys()
		try:
			self._proxy.connect('g-signal', self._handle_media_keys)
		except GLib.GError:
            # We cannot grab media keys if no settings daemon is running
			pass

	"""
		Do key grabbing
	"""
	def _grab_media_player_keys(self):
		try:
			self._proxy.call_sync('GrabMediaPlayerKeys',
								 GLib.Variant('(su)', ('Lollypop', 0)),
								 Gio.DBusCallFlags.NONE,
								 -1,
								 None)
		except GLib.GError:
			# We cannot grab media keys if no settings daemon is running
			pass

	"""
		Do player actions in response to media key pressed
	"""
	def _handle_media_keys(self, proxy, sender, signal, parameters):
		if signal != 'MediaPlayerKeyPressed':
			print('Received an unexpected signal \'%s\' from media player'.format(signal))
			return
		response = parameters.get_child_value(1).get_string()
		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.prev()
	
	"""
		Setup window icon, position and size, callback for updating this values
	"""
	def _setup_window(self):
		self.set_size_request(200, 100)
		self.set_icon_name('lollypop')
		size_setting = self._settings.get_value('window-size')
		if isinstance(size_setting[0], int) and isinstance(size_setting[1], int):
			self.resize(size_setting[0], size_setting[1])

		position_setting = self._settings.get_value('window-position')
		if len(position_setting) == 2 \
			and isinstance(position_setting[0], int) \
			and isinstance(position_setting[1], int):
			self.move(position_setting[0], position_setting[1])

		if self._settings.get_value('window-maximized'):
			self.maximize()

		self.connect("window-state-event", self._on_window_state_event)
		self.connect("configure-event", self._on_configure_event)

	"""
		Setup window main view:
			- genre list
			- artist list
			- main view as artist view or album view
	"""
	def _setup_view(self):
		self._box = Gtk.Grid()
		self._toolbar = Toolbar(self._db, self._player)
		self.set_titlebar(self._toolbar.header_bar)
		self._toolbar.header_bar.show()
		self._toolbar.get_infobox().connect("button-press-event", self._show_current_album)

		self._list_genres = SelectionList("Genre", 150)
		self._list_artists = SelectionList("Artist", 200)
		
		self._view = LoadingView()

		separator = Gtk.Separator()
		separator.show()
		self._box.add(self._list_genres.widget)
		self._box.add(separator)
		self._box.add(self._list_artists.widget)
		self._box.add(self._view)
		self.add(self._box)
		self._box.show()
		self.show()
	
	"""
		Run collection update on mapped window
		Pass _update_genre() as collection scanned callback
	"""	
	def _on_mapped_window(self, obj, data):
		if self._db.is_empty():
			self._scanner.update(self._update_genres)
		else:
			genres = self._db.get_all_genres()
			self._update_genres(genres)
		
	"""
		Update genres list with genres
	"""
	def _update_genres(self, genres):
		genres.insert(0, (-1, _("All genres")))
		genres.insert(0, (-2, _("Populars albums")))
		self._list_genres.populate(genres)
		self._list_genres.connect('item-selected', self._update_artists)
		self._list_genres.widget.show()
		self._list_genres.select_first()

	"""
		Update artist list for genre_id
	"""
	def _update_artists(self, obj, genre_id):
		if self._artist_signal_id:
			self._list_artists.disconnect(self._artist_signal_id)
		if genre_id == -1:
			self._list_artists.populate(self._db.get_all_artists(), True)
			self._update_view_albums(self, -1)
			self._list_artists.widget.show()
		elif genre_id == -2:
			self._update_view_populars_albums()
			self._list_artists.widget.hide()
		else:
			self._list_artists.populate(self._db.get_artists_by_genre_id(genre_id), True)
			self._update_view_albums(self, genre_id)
			self._list_artists.widget.show()
		self._artist_signal_id = self._list_artists.connect('item-selected', self._update_view_artist)
		self._genre_id = genre_id

	"""
		Update artist view for artist_id
	"""
	def _update_view_artist(self, obj, artist_id):
		self._box.remove(self._view)
		self._view.destroy()
		self._view = ArtistView(self._db, self._player, self._genre_id, artist_id)
		self._box.add(self._view)
		self._view.populate()
	
	"""
		Update albums view with populars albums
	"""
	def _update_view_populars_albums(self):
		self._box.remove(self._view)
		self._view.destroy()
		self._view = AlbumView(self._db, self._player, None)
		self._box.add(self._view)
		self._view.populate_popular()
	"""
		Update albums view for genre_id
	"""
	def _update_view_albums(self, obj, genre_id):
		self._box.remove(self._view)
		self._view.destroy()
		self._view = AlbumView(self._db, self._player, genre_id)
		self._box.add(self._view)
		self._view.populate()
	
	"""
		Save new window size/position
	"""		
	def _on_configure_event(self, widget, event):
		size = widget.get_size()
		self._settings.set_value('window-size', GLib.Variant('ai', [size[0], size[1]]))

		position = widget.get_position()
		self._settings.set_value('window-position', GLib.Variant('ai', [position[0], position[1]]))

	"""
		Save maximised state
	"""
	def _on_window_state_event(self, widget, event):
		self._settings.set_boolean('window-maximized', 'GDK_WINDOW_STATE_MAXIMIZED' in event.new_window_state.value_names)

	"""
		Show current album context/content
	"""
	def _show_current_album(self, obj, data):
		track_id = self._player.get_current_track_id()
		if  track_id != -1:
			self._view.current_changed(False, track_id)