def errorhook(exc_info=None): """This is the main entry point Call in an exception context. Thread safe. def my_thread(): try: do_work() except Exception: errorhook() """ global _error_lock, _errorhook_enabled if not _errorhook_enabled: return if exc_info is None: exc_info = sys.exc_info() if exc_info[0] is None: # called outside of an exception context, just ignore print_e("no active exception!") return # In case something goes wrong during error handling print it first print_exc(exc_info) if not _error_lock.acquire(False): # Make sure only one of these is active at a time return # write error and logs to disk dump_dir = os.path.join(quodlibet.get_user_dir(), "dumps") dump_to_disk(dump_dir, exc_info) sentry = get_sentry() # For crashes the stack trace is not enough to differentiating different # crash sources. We need to give our own grouping key (fingerprint) based # on the stack trace provided by faulthandler. fingerprint = None if isinstance(exc_info[1], FaultHandlerCrash): fingerprint = ["{{ default }}", exc_info[1].get_grouping_key()] try: sentry_error = sentry.capture(exc_info, fingerprint=fingerprint) except SentryError: sentry_error = None def called_in_main_thread(): try: run_error_dialogs(exc_info, sentry_error) finally: _error_lock.release() if is_main_thread(): called_in_main_thread() else: GLib.idle_add(called_in_main_thread)
def validator(pat): """Validates Patterns a bit. TODO: Extract to somewhere good - see #1983""" try: return bool(Pattern(pat).format(DUMMY_AF)) except Exception as e: print_e("Problem with %s" % (pat, ), e)
def _init_gst(): """Call once before importing GStreamer""" assert "gi.repository.Gst" not in sys.modules import gi # We don't want python-gst, it changes API.. assert "gi.overrides.Gst" not in sys.modules sys.modules["gi.overrides.Gst"] = None # blacklist some modules, simply loading can cause segfaults sys.modules["gst"] = None # We don't depend on Gst overrides, so make sure it's initialized. try: gi.require_version("Gst", "1.0") from gi.repository import Gst except (ValueError, ImportError): return if Gst.is_initialized(): return from gi.repository import GLib try: ok, argv[:] = Gst.init_check(argv) except GLib.GError: print_e("Failed to initialize GStreamer") # Uninited Gst segfaults: make sure no one can use it sys.modules["gi.repository.Gst"] = None else: # monkey patching ahead _fix_gst_leaks()
def __invoke(self, librarian, event, *args): args = list(args) if args and args[0]: if isinstance(args[0], dict): args[0] = SongWrapper(args[0]) elif isinstance(args[0], (set, list)): args[0] = ListWrapper(args[0]) for plugin in itervalues(self.__plugins): method_name = 'plugin_on_' + event.replace('-', '_') handler = getattr(plugin, method_name, None) def overridden(obj, name): return name in type(obj).__dict__ if overridden(plugin, method_name): try: handler(*args) except Exception: print_e("Error during %s on %s" % (method_name, type(plugin))) util.print_exc() if event not in ["removed", "changed"] and args: from quodlibet import app songs = args[0] if not isinstance(songs, (set, list)): songs = [songs] songs = filter(None, songs) check_wrapper_changed(librarian, app.window, songs)
def __invoke(self, librarian, event, *args): args = list(args) if args and args[0]: if isinstance(args[0], dict): args[0] = SongWrapper(args[0]) elif isinstance(args[0], (set, list)): args[0] = ListWrapper(args[0]) for plugin in list(self.__plugins.values()): method_name = 'plugin_on_' + event.replace('-', '_') handler = getattr(plugin, method_name, None) def overridden(obj, name): return name in type(obj).__dict__ if overridden(plugin, method_name): try: handler(*args) except Exception: print_e("Error during %s on %s" % (method_name, type(plugin))) errorhook() if event not in ["removed", "changed"] and args: from quodlibet import app songs = args[0] if not isinstance(songs, (set, list)): songs = [songs] songs = filter(None, songs) check_wrapper_changed(librarian, app.window, songs)
def validator(pat): """Validates Patterns a bit. TODO: Extract to somewhere good - see #1983""" try: str = Pattern(pat).format(DUMMY_AF) return bool(str) except Exception as e: print_e("Problem with %s" % (pat,), e)
def __set_mode(self, player, mode): selected_mode = next((m for m in self.__modes if m[0] == mode), None) if selected_mode is None: print_e("Invalid selected replaygain mode: %r" % mode) selected_mode = self.__modes[0] print_e("Falling back to replaygain mode: %r" % selected_mode[0]) player.replaygain_profiles[0] = selected_mode[2] player.reset_replaygain()
def download_taglist(callback, cofuncid, step=1024 * 10): """Generator for loading the bz2 compressed tag list. Calls callback with the decompressed data or None in case of an error.""" with Task(_("Internet Radio"), _("Downloading station list")) as task: if cofuncid: task.copool(cofuncid) try: response = urlopen(STATION_LIST_URL) except (EnvironmentError, HTTPException) as e: print_e("Failed fetching from %s" % STATION_LIST_URL, e) GLib.idle_add(callback, None) return try: size = int(response.info().get("content-length", 0)) except ValueError: size = 0 decomp = bz2.BZ2Decompressor() data = b"" temp = b"" read = 0 while temp or not data: read += len(temp) if size: task.update(float(read) / size) else: task.pulse() yield True try: data += decomp.decompress(temp) temp = response.read(step) except (IOError, EOFError): data = None break response.close() yield True stations = None if data: stations = parse_taglist(data) GLib.idle_add(callback, stations)
def plugin_songs(self, songs): # Check this is a launch, not a configure if self.com_index: com = self.get_data(self.com_index) print_d("Running %s" % com) try: com.run(songs) except Exception as err: print_e("Couldn't run command %s: %s %s at" % (com.name, type(err), err)) ErrorMessage( self.plugin_window, _("Unable to run custom command %s" % util.escape(self.com_index)), util.escape(str(err))).run()
def plugin_songs(self, songs): # Check this is a launch, not a configure if self.com_index: com = self.get_data(self.com_index) print_d("Running %s" % com) try: com.run(songs) except Exception, err: print_e("Couldn't run command %s: %s %s at" % (com.name, type(err), err)) ErrorMessage( self.plugin_window, _("Unable to run custom command %s" % util.escape(self.com_index)), util.escape(str(err))).run()
def parse(self, args=None): if args is None: args = argv[1:] from getopt import getopt, GetoptError try: opts, args = getopt(args, self.__shorts(), self.__longs()) except GetoptError as s: s = str(s) text = [] if "not recognized" in s: text.append( _("Option %r not recognized.") % s.split()[1]) elif "requires argument" in s: text.append( _("Option %r requires an argument.") % s.split()[1]) elif "unique prefix" in s: text.append( _("%r is not a unique prefix.") % s.split()[1]) if "help" in self.__args: text.append(_("Try %s --help.") % argv[0]) print_e("\n".join(text)) raise SystemExit(True) else: transopts = {} for o, a in opts: if o.startswith("--"): o = self.__translate_long.get(o[2:], o[2:]) elif o.startswith("-"): o = self.__translate_short.get(o[1:], o[1:]) if o == "help": print_(self.help()) raise SystemExit elif o == "version": print_(self.version()) raise SystemExit elif o == "debug": from quodlibet import const const.DEBUG = True if self.__args[o]: transopts[o] = a else: transopts[o] = True return transopts, args
def _get_stations_from(uri: str, on_done: Callable[[Iterable[IRFile], str], None])\ -> None: """Fetches the URI content and extracts IRFiles Called from thread - so no direct GTK+ interaction :param uri: URI of station :param on_done: a callback taking files when done (or none if errored) """ with Task(_("Internet Radio"), _("Add stations")) as task: irfs: Collection[IRFile] = [] GLib.idle_add(task.pulse) if (uri.lower().endswith(".pls") or uri.lower().endswith(".m3u") or uri.lower().endswith(".m3u8")): if not re.match('^([^/:]+)://', uri): # Assume HTTP if no protocol given. See #2731 uri = 'http://' + uri print_d("Assuming http: %s" % uri) # Error handling outside sock = None GLib.idle_add(task.pulse) _fn, ext = splitext(uri.lower()) try: sock = urlopen(uri, timeout=6) if ext == ".pls": irfs = parse_pls(sock) elif ext in (".m3u", ".m3u8"): irfs = parse_m3u(sock) GLib.idle_add(task.pulse) except IOError as e: print_e(f"Couldn't download from {uri} ({e})") finally: if sock: sock.close() else: try: irfs = [IRFile(uri)] except ValueError as e: print_e("Can't add URI %s" % uri, e) on_done(irfs, uri)
def _gst_init(): """Call once before importing GStreamer""" assert "gi.repository.Gst" not in sys.modules import gi # We don't want python-gst, it changes API.. assert "gi.overrides.Gst" not in sys.modules sys.modules["gi.overrides.Gst"] = None # blacklist some modules, simply loading can cause segfaults sys.modules["gst"] = None # We don't depend on Gst overrides, so make sure it's initialized. try: gi.require_version("Gst", "1.0") from gi.repository import Gst except (ValueError, ImportError): return if Gst.is_initialized(): return from gi.repository import GLib try: ok, argv = Gst.init_check(sys.argv) except GLib.GError: print_e("Failed to initialize GStreamer") # Uninited Gst segfaults: make sure no one can use it sys.modules["gi.repository.Gst"] = None else: sys.argv = argv # monkey patching ahead _fix_gst_leaks() # https://bugzilla.gnome.org/show_bug.cgi?id=710447 import threading threading.Thread(target=lambda: None).start()
def _handle_songs(self, songs, playlist=None): # Check this is a launch, not a configure if self.com_index: com = self.get_data(self.com_index) if len(songs) > com.warn_threshold: if not confirm_multi_song_invoke( self, com.name, len(songs)): print_d("User decided not to run on %d songs" % len(songs)) return print_d("Running %s on %d song(s)" % (com, len(songs))) try: com.run(songs, playlist and playlist.name) except Exception as err: print_e("Couldn't run command %s: %s %s at:" % (com.name, type(err), err, )) print_exc() ErrorMessage( self.plugin_window, _("Unable to run custom command %s") % util.escape(self.com_index), util.escape(str(err))).run()
def populate_menu(self, menu, library, browser, playlists): """Appends items onto `menu` for each enabled playlist plugin, separated as necessary. """ top_parent = qltk.get_top_parent(browser) attrs = ['plugin_playlist', 'plugin_playlists'] if len(playlists) == 1: attrs.append('plugin_single_playlist') items = [] kinds = self.__plugins kinds.sort(key=lambda plugin: plugin.PLUGIN_ID) print_d("Found %d Playlist plugin(s): %s" % (len(kinds), kinds)) for Kind in kinds: usable = any([callable(getattr(Kind, s)) for s in attrs]) if usable: try: items.append(Kind(playlists, library, top_parent)) except: print_e("Couldn't initialise playlist plugin %s: " % Kind) print_exc() items = filter(lambda i: i.initialized, items) if items: menu.append(SeparatorMenuItem()) for item in items: try: menu.append(item) args = (library, browser, playlists) if item.get_submenu(): for subitem in item.get_submenu().get_children(): subitem.connect_object('activate', self.__handle, item, *args) else: item.connect('activate', self.__handle, *args) except: print_exc() item.destroy()
def _init_gst(): """Call once before importing GStreamer""" arch_key = "64" if sys.maxsize > 2**32 else "32" registry_name = "gst-registry-%s-%s.bin" % (sys.platform, arch_key) environ["GST_REGISTRY"] = os.path.join(get_cache_dir(), registry_name) assert "gi.repository.Gst" not in sys.modules import gi # We don't want python-gst, it changes API.. assert "gi.overrides.Gst" not in sys.modules sys.modules["gi.overrides.Gst"] = None # blacklist some modules, simply loading can cause segfaults sys.modules["gst"] = None # We don't depend on Gst overrides, so make sure it's initialized. try: gi.require_version("Gst", "1.0") from gi.repository import Gst except (ValueError, ImportError): return if Gst.is_initialized(): return from gi.repository import GLib try: ok, argv[:] = Gst.init_check(argv) except GLib.GError: print_e("Failed to initialize GStreamer") # Uninited Gst segfaults: make sure no one can use it sys.modules["gi.repository.Gst"] = None else: # monkey patching ahead _fix_gst_leaks()
def populate_menu(self, menu, library, browser, playlists): """Appends items onto `menu` for each enabled playlist plugin, separated as necessary. """ attrs = ['plugin_playlist', 'plugin_playlists'] if len(playlists) == 1: attrs.append('plugin_single_playlist') items = [] kinds = self.__plugins kinds.sort(key=lambda plugin: plugin.PLUGIN_ID) print_d("Found %d Playlist plugin(s): %s" % (len(kinds), kinds)) for Kind in kinds: usable = any([callable(getattr(Kind, s)) for s in attrs]) if usable: try: items.append(Kind(playlists, library)) except: print_e("Couldn't initialise playlist plugin %s: " % Kind) print_exc() items = [i for i in items if i.initialized] if items: menu.append(SeparatorMenuItem()) for item in items: try: menu.append(item) args = (library, browser, playlists) if item.get_submenu(): for subitem in item.get_submenu().get_children(): subitem.connect( 'activate', self.__on_activate, item, *args) else: item.connect( 'activate', self.__on_activate, item, *args) except: print_exc() item.destroy()
def _init_gtk(): """Call before using Gtk/Gdk""" import gi if config.getboolean("settings", "pangocairo_force_fontconfig") and \ "PANGOCAIRO_BACKEND" not in environ: environ["PANGOCAIRO_BACKEND"] = "fontconfig" # disable for consistency and trigger events seem a bit flaky here if config.getboolean("settings", "scrollbar_always_visible"): environ["GTK_OVERLAY_SCROLLING"] = "0" try: # not sure if this is available under Windows gi.require_version("GdkX11", "3.0") from gi.repository import GdkX11 GdkX11 except (ValueError, ImportError): pass gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") gi.require_version("Pango", "1.0") gi.require_version('Soup', '2.4') gi.require_version('PangoCairo', "1.0") from gi.repository import Gtk from quodlibet.qltk import ThemeOverrider, gtk_version # PyGObject doesn't fail anymore when init fails, so do it ourself initialized, argv[:] = Gtk.init_check(argv) if not initialized: raise SystemExit("Gtk.init failed") # include our own icon theme directory theme = Gtk.IconTheme.get_default() theme_search_path = get_image_dir() assert os.path.exists(theme_search_path) theme.append_search_path(theme_search_path) # Force menu/button image related settings. We might show too many atm # but this makes sure we don't miss cases where we forgot to force them # per widget. # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # some day... but not now warnings.filterwarnings('ignore', '.*Stock items are deprecated.*', Warning) warnings.filterwarnings('ignore', '.*:use-stock.*', Warning) warnings.filterwarnings( 'ignore', r'.*The property GtkAlignment:[^\s]+ is deprecated.*', Warning) settings = Gtk.Settings.get_default() with warnings.catch_warnings(): warnings.simplefilter("ignore") settings.set_property("gtk-button-images", True) settings.set_property("gtk-menu-images", True) if hasattr(settings.props, "gtk_primary_button_warps_slider"): # https://bugzilla.gnome.org/show_bug.cgi?id=737843 settings.set_property("gtk-primary-button-warps-slider", True) # Make sure PyGObject includes support for foreign cairo structs try: gi.require_foreign("cairo") except ImportError: print_e("PyGObject is missing cairo support") exit(1) css_override = ThemeOverrider() if sys.platform == "darwin": # fix duplicated shadows for popups with Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" GtkWindow { box-shadow: none; } .tooltip { border-radius: 0; padding: 0; } .tooltip.background { background-clip: border-box; } """) css_override.register_provider("", style_provider) if gtk_version[:2] >= (3, 20): # https://bugzilla.gnome.org/show_bug.cgi?id=761435 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" spinbutton, button { min-height: 22px; } .view button { min-height: 24px; } entry { min-height: 28px; } entry.cell { min-height: 0; } """) css_override.register_provider("Adwaita", style_provider) css_override.register_provider("HighContrast", style_provider) # https://github.com/quodlibet/quodlibet/issues/2541 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" treeview.view.separator { min-height: 2px; color: @borders; } """) css_override.register_provider("Ambiance", style_provider) css_override.register_provider("Radiance", style_provider) # https://github.com/quodlibet/quodlibet/issues/2677 css_override.register_provider("Clearlooks-Phenix", style_provider) # https://github.com/quodlibet/quodlibet/issues/2997 css_override.register_provider("Breeze", style_provider) if gtk_version[:2] >= (3, 18): # Hack to get some grab handle like thing for panes style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" GtkPaned.vertical, paned.vertical >separator { -gtk-icon-source: -gtk-icontheme("view-more-symbolic"); -gtk-icon-transform: rotate(90deg) scaleX(0.1) scaleY(3); } GtkPaned.horizontal, paned.horizontal >separator { -gtk-icon-source: -gtk-icontheme("view-more-symbolic"); -gtk-icon-transform: rotate(0deg) scaleX(0.1) scaleY(3); } """) css_override.register_provider("", style_provider) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # blacklist some modules, simply loading can cause segfaults sys.modules["gtk"] = None sys.modules["gpod"] = None sys.modules["gnome"] = None from quodlibet.qltk import pygobject_version, gtk_version MinVersions.GTK.check(gtk_version) MinVersions.PYGOBJECT.check(pygobject_version)
def _init_gtk(): """Call before using Gtk/Gdk""" import gi # pygiaio 3.14rev16 switched to fontconfig for PangoCairo. As this results # in 100% CPU under win7 revert it. Maybe we need to update the # cache in the windows installer for it to work... but for now revert. if is_windows(): environ['PANGOCAIRO_BACKEND'] = 'win32' environ["GTK_CSD"] = "0" # disable for consistency and trigger events seem a bit flaky here if is_osx(): environ["GTK_OVERLAY_SCROLLING"] = "0" # make sure GdkX11 doesn't get used under Windows if os.name == "nt": sys.modules["gi.repository.GdkX11"] = None try: # not sure if this is available under Windows gi.require_version("GdkX11", "3.0") from gi.repository import GdkX11 GdkX11 except (ValueError, ImportError): pass gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") gi.require_version("Pango", "1.0") gi.require_version('Soup', '2.4') from gi.repository import Gtk, Soup from quodlibet.qltk import ThemeOverrider, gtk_version # Work around missing annotation in older libsoup (Ubuntu 14.04 at least) message = Soup.Message() try: message.set_request(None, Soup.MemoryUse.COPY, b"") except TypeError: orig = Soup.Message.set_request def new_set_request(self, content_type, req_use, req_body): return orig(self, content_type, req_use, req_body, len(req_body)) Soup.Message.set_request = new_set_request # PyGObject doesn't fail anymore when init fails, so do it ourself initialized, argv[:] = Gtk.init_check(argv) if not initialized: raise SystemExit("Gtk.init failed") # include our own icon theme directory theme = Gtk.IconTheme.get_default() theme_search_path = get_image_dir() assert os.path.exists(theme_search_path) theme.append_search_path(theme_search_path) # Force menu/button image related settings. We might show too many atm # but this makes sure we don't miss cases where we forgot to force them # per widget. # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # some day... but not now warnings.filterwarnings( 'ignore', '.*Stock items are deprecated.*', Warning) warnings.filterwarnings( 'ignore', '.*:use-stock.*', Warning) warnings.filterwarnings( 'ignore', '.*The property GtkAlignment:[^\s]+ is deprecated.*', Warning) settings = Gtk.Settings.get_default() with warnings.catch_warnings(): warnings.simplefilter("ignore") settings.set_property("gtk-button-images", True) settings.set_property("gtk-menu-images", True) if hasattr(settings.props, "gtk_primary_button_warps_slider"): # https://bugzilla.gnome.org/show_bug.cgi?id=737843 settings.set_property("gtk-primary-button-warps-slider", True) # Make sure PyGObject includes support for foreign cairo structs try: gi.require_foreign("cairo") except AttributeError: # older pygobject pass except ImportError: print_e("PyGObject is missing cairo support") exit(1) css_override = ThemeOverrider() # CSS overrides if os.name == "nt": # somehow borders are missing under Windows & Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" .menu { border: 1px solid @borders; } """) css_override.register_provider("", style_provider) if sys.platform == "darwin": # fix duplicated shadows for popups with Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" GtkWindow { box-shadow: none; } .tooltip { border-radius: 0; padding: 0; } .tooltip.background { background-clip: border-box; } """) css_override.register_provider("", style_provider) if gtk_version[:2] >= (3, 20): # https://bugzilla.gnome.org/show_bug.cgi?id=761435 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" spinbutton, button { min-height: 1.8rem; } .view button { min-height: 2.0rem; } entry { min-height: 2.4rem; } entry.cell { min-height: 0; } """) css_override.register_provider("Adwaita", style_provider) css_override.register_provider("HighContrast", style_provider) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # blacklist some modules, simply loading can cause segfaults sys.modules["gtk"] = None sys.modules["gpod"] = None sys.modules["gnome"] = None from quodlibet.qltk import pygobject_version, gtk_version MinVersions.GTK.check(gtk_version) MinVersions.PYGOBJECT.check(pygobject_version)
def _moveart(self, art_sets, pathfile_old, pathfile_new, song): path_old = os.path.dirname(os.path.realpath(pathfile_old)) path_new = os.path.dirname(os.path.realpath(pathfile_new)) if os.path.realpath(path_old) == os.path.realpath(path_new): return if (path_old in art_sets.keys() and not art_sets[path_old]): return # get art set for path images = [] if path_old in art_sets.keys(): images = art_sets[path_old] else: def glob_escape(s): for c in '[*?': s = s.replace(c, '[' + c + ']') return s # generate art set for path art_sets[path_old] = images path_old_escaped = glob_escape(path_old) for suffix in self.IMAGE_EXTENSIONS: images.extend(glob.glob(os.path.join(path_old_escaped, "*." + suffix))) if images: # set not empty yet, (re)process filenames = config.getstringlist("albumart", "search_filenames") moves = [] for fn in filenames: fn = os.path.join(path_old, fn) if "<" in fn: # resolve path fnres = ArbitraryExtensionFileFromPattern(fn).format(song) if fnres in images and fnres not in moves: moves.append(fnres) elif "*" in fn: moves.extend(f for f in glob.glob(fn) if f in images and f not in moves) elif fn in images and fn not in moves: moves.append(fn) if len(moves) > 0: overwrite = config.getboolean("rename", "move_art_overwrite") for fnmove in moves: try: # existing files safeguarded until move successful, # then deleted if overwrite set fnmoveto = os.path.join(path_new, os.path.split(fnmove)[1]) fnmoveto_orig = "" if os.path.exists(fnmoveto): fnmoveto_orig = fnmoveto + ".orig" if not os.path.exists(fnmoveto_orig): os.rename(fnmoveto, fnmoveto_orig) else: suffix = 1 while os.path.exists(fnmoveto_orig + "." + str(suffix)): suffix += 1 fnmoveto_orig = (fnmoveto_orig + "." + str(suffix)) os.rename(fnmoveto, fnmoveto_orig) print_d(f"Renaming image {fnmove!r} to {fnmoveto!r}", self) shutil.move(fnmove, fnmoveto) if overwrite and fnmoveto_orig: os.remove(fnmoveto_orig) images.remove(fnmove) except Exception as e: print_e(f"Couldn't move file ({e})") util.print_exc()
def _init_gtk(): """Call before using Gtk/Gdk""" import gi # pygiaio 3.14rev16 switched to fontconfig for PangoCairo. As this results # in 100% CPU under win7 revert it. Maybe we need to update the # cache in the windows installer for it to work... but for now revert. if is_windows(): os.environ['PANGOCAIRO_BACKEND'] = 'win32' # disable for consistency and trigger events seem a bit flaky here if is_osx(): os.environ["GTK_OVERLAY_SCROLLING"] = "0" # make sure GdkX11 doesn't get used under Windows if os.name == "nt": sys.modules["gi.repository.GdkX11"] = None try: # not sure if this is available under Windows gi.require_version("GdkX11", "3.0") from gi.repository import GdkX11 GdkX11 except (ValueError, ImportError): pass gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") gi.require_version("Pango", "1.0") gi.require_version('Soup', '2.4') from gi.repository import Gtk, Gdk # PyGObject doesn't fail anymore when init fails, so do it ourself initialized, argv = Gtk.init_check(sys.argv) if not initialized: raise SystemExit("Gtk.init failed") sys.argv = list(argv) # add Gtk.TreePath.__getitem__/__len__ for PyGObject 3.2 try: Gtk.TreePath()[0] except TypeError: Gtk.TreePath.__getitem__ = lambda self, index: list(self)[index] Gtk.TreePath.__len__ = lambda self: self.get_depth() # GTK+ 3.4+ constants if not hasattr(Gdk, "BUTTON_PRIMARY"): Gdk.BUTTON_PRIMARY = 1 Gdk.BUTTON_MIDDLE = 2 Gdk.BUTTON_SECONDARY = 3 if not hasattr(Gdk, "EVENT_PROPAGATE"): Gdk.EVENT_PROPAGATE = 0 Gdk.EVENT_STOP = 1 # include our own icon theme directory theme = Gtk.IconTheme.get_default() theme_search_path = get_image_dir() assert os.path.exists(theme_search_path) theme.append_search_path(theme_search_path) # Force menu/button image related settings. We might show too many atm # but this makes sure we don't miss cases where we forgot to force them # per widget. # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # some day... but not now warnings.filterwarnings( 'ignore', '.*Stock items are deprecated.*', Warning) warnings.filterwarnings( 'ignore', '.*:use-stock.*', Warning) warnings.filterwarnings( 'ignore', '.*The property GtkAlignment:[^\s]+ is deprecated.*', Warning) settings = Gtk.Settings.get_default() with warnings.catch_warnings(): warnings.simplefilter("ignore") settings.set_property("gtk-button-images", True) settings.set_property("gtk-menu-images", True) if hasattr(settings.props, "gtk_primary_button_warps_slider"): # https://bugzilla.gnome.org/show_bug.cgi?id=737843 settings.set_property("gtk-primary-button-warps-slider", True) # Make sure PyGObject includes support for foreign cairo structs try: gi.require_foreign("cairo") except AttributeError: # older pygobject pass except ImportError: print_e("PyGObject is missing cairo support") exit(1) # CSS overrides if os.name == "nt": # somehow borders are missing under Windows & Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(""" .menu { border: 1px solid @borders; } """) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) if sys.platform == "darwin": # fix duplicated shadows for popups with Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(""" GtkWindow { box-shadow: none; } .tooltip { border-radius: 0; padding: 0; } .tooltip.background { background-clip: border-box; } """) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # blacklist some modules, simply loading can cause segfaults sys.modules["gtk"] = None sys.modules["gpod"] = None sys.modules["gnome"] = None from quodlibet.qltk import pygobject_version, gtk_version MinVersions.GTK.check(gtk_version) MinVersions.PYGOBJECT.check(pygobject_version)
def _gtk_init(icon=None): import gi try: # not sure if this is available under Windows gi.require_version("GdkX11", "3.0") from gi.repository import GdkX11 except (ValueError, ImportError): pass gi.require_version("GLib", "2.0") gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") gi.require_version("GObject", "2.0") gi.require_version("Pango", "1.0") gi.require_version("GdkPixbuf", "2.0") gi.require_version("Gio", "2.0") from gi.repository import Gtk, GObject, GLib, Gdk # add Gtk.TreePath.__getitem__/__len__ for PyGObject 3.2 try: Gtk.TreePath()[0] except TypeError: Gtk.TreePath.__getitem__ = lambda self, index: list(self)[index] Gtk.TreePath.__len__ = lambda self: self.get_depth() # GTK+ 3.4+ constants if not hasattr(Gdk, "BUTTON_PRIMARY"): Gdk.BUTTON_PRIMARY = 1 Gdk.BUTTON_MIDDLE = 2 Gdk.BUTTON_SECONDARY = 3 # Force menu/button image related settings. We might show too many atm # but this makes sure we don't miss cases where we forgot to force them # per widget. settings = Gtk.Settings.get_default() settings.set_property("gtk-button-images", True) settings.set_property("gtk-menu-images", True) # Make sure PyGObject includes support for foreign cairo structs some_window = Gtk.OffscreenWindow() some_window.show() try: some_window.get_surface() except TypeError: print_e("PyGObject is missing cairo support") exit(1) # CSS overrides style_provider = Gtk.CssProvider() style_provider.load_from_data(""" /* Make GtkPaned look like in <=3.12, we depend on the spacing */ GtkPaned { -GtkPaned-handle-size: 6; background-image: none; margin: 0; } """) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # We don't want python-gst, it changes API.. assert "gi.overrides.Gst" not in sys.modules sys.modules["gi.overrides.Gst"] = None # We don't depend on Gst overrides, so make sure it's initialized. try: gi.require_version("Gst", "1.0") from gi.repository import Gst except (ValueError, ImportError): pass else: if not Gst.is_initialized(): try: ok, argv = Gst.init_check(sys.argv) except GLib.GError: print_e("Failed to initialize GStreamer") # Uninited Gst segfaults: make sure no one can use it sys.modules["gi.repository.Gst"] = None else: sys.argv = argv # https://bugzilla.gnome.org/show_bug.cgi?id=710447 import threading threading.Thread(target=lambda: None).start() # some code depends on utf-8 default encoding (pygtk used to set it) reload(sys) sys.setdefaultencoding("utf-8") # blacklist some modules, simply loading can cause segfaults sys.modules["gtk"] = None sys.modules["gpod"] = None sys.modules["glib"] = None sys.modules["gobject"] = None sys.modules["gnome"] = None from quodlibet.qltk import pygobject_version if pygobject_version < (3, 9): GObject.threads_init() theme = Gtk.IconTheme.get_default() theme.append_search_path(quodlibet.const.IMAGEDIR) if icon: Gtk.Window.set_default_icon_name(icon)
def process_arguments(argv): from quodlibet.util.uri import URI from quodlibet import util from quodlibet import const actions = [] controls = [ "next", "previous", "play", "pause", "play-pause", "stop", "hide-window", "show-window", "toggle-window", "focus", "quit", "unfilter", "refresh", "force-previous" ] controls_opt = [ "seek", "order", "repeat", "query", "volume", "filter", "set-rating", "set-browser", "open-browser", "random", "song-list", "queue" ] options = util.OptionParser("Quod Libet", const.VERSION, _("a music library and player"), _("[option]")) options.add("print-playing", help=_("Print the playing song and exit")) options.add("start-playing", help=_("Begin playing immediately")) for opt, help in [ ("next", _("Jump to next song")), ("previous", _("Jump to previous song or restart if near the beginning")), ("force-previous", _("Jump to previous song")), ("play", _("Start playback")), ("pause", _("Pause playback")), ("play-pause", _("Toggle play/pause mode")), ("stop", _("Stop playback")), ("volume-up", _("Turn up volume")), ("volume-down", _("Turn down volume")), ("status", _("Print player status")), ("hide-window", _("Hide main window")), ("show-window", _("Show main window")), ("toggle-window", _("Toggle main window visibility")), ("focus", _("Focus the running player")), ("unfilter", _("Remove active browser filters")), ("refresh", _("Refresh and rescan library")), ("list-browsers", _("List available browsers")), ("print-playlist", _("Print the current playlist")), ("print-queue", _("Print the contents of the queue")), ("no-plugins", _("Start without plugins")), ("run", _("Start Quod Libet if it isn't running")), ("quit", _("Exit Quod Libet")), ]: options.add(opt, help=help) for opt, help, arg in [ ("seek", _("Seek within the playing song"), _("[+|-][HH:]MM:SS")), ("order", _("Set or toggle the playback order"), "[order]|toggle"), ("repeat", _("Turn repeat off, on, or toggle it"), "0|1|t"), ("volume", _("Set the volume"), "(+|-|)0..100"), ("query", _("Search your audio library"), _("query")), ("play-file", _("Play a file"), C_("command", "filename")), ("set-rating", _("Rate the playing song"), "0.0..1.0"), ("set-browser", _("Set the current browser"), "BrowserName"), ("open-browser", _("Open a new browser"), "BrowserName"), ("queue", _("Show or hide the queue"), "on|off|t"), ("song-list", _("Show or hide the main song list"), "on|off|t"), ("random", _("Filter on a random value"), C_("command", "tag")), ("filter", _("Filter on a tag value"), _("tag=value")), ("enqueue", _("Enqueue a file or query"), "%s|%s" % (C_("command", "filename"), _("query"))), ("enqueue-files", _("Enqueue comma-separated files"), "%s[,%s..]" % (_("filename"), _("filename"))), ("print-query", _("Print filenames of results of query to stdout"), _("query")), ("unqueue", _("Unqueue a file or query"), "%s|%s" % (C_("command", "filename"), _("query"))), ]: options.add(opt, help=help, arg=arg) options.add("sm-config-prefix", arg="dummy") options.add("sm-client-id", arg="prefix") options.add("screen", arg="dummy") def is_vol(str): if str[0] in '+-': if len(str) == 1: return True str = str[1:] return str.isdigit() def is_time(str): if str[0] not in "+-0123456789": return False elif str[0] in "+-": str = str[1:] parts = str.split(":") if len(parts) > 3: return False else: return not (False in [p.isdigit() for p in parts]) def is_float(str): try: float(str) except ValueError: return False else: return True validators = { "order": ["0", "1", "t", "toggle", "inorder", "shuffle", "weighted", "onesong"].__contains__, "repeat": ["0", "1", "t", "on", "off", "toggle"].__contains__, "volume": is_vol, "seek": is_time, "set-rating": is_float, } cmds_todo = [] def queue(*args): cmds_todo.append(args) # XXX: to make startup work in case the desktop file isn't passed # a file path/uri if argv[-1] == "--play-file": argv = argv[:-1] opts, args = options.parse(argv[1:]) for command, arg in opts.items(): if command in controls: queue(command) elif command in controls_opt: if command in validators and not validators[command](arg): print_e(_("Invalid argument for '%s'.") % command) print_e(_("Try %s --help.") % sys.argv[0]) exit_(True, notify_startup=True) else: queue(command, arg) elif command == "status": queue("status") elif command == "print-playlist": queue("dump-playlist") elif command == "print-queue": queue("dump-queue") elif command == "list-browsers": queue("dump-browsers") elif command == "volume-up": queue("volume +") elif command == "volume-down": queue("volume -") elif command == "enqueue" or command == "unqueue": try: filename = URI(arg).filename except ValueError: filename = arg queue(command, filename) elif command == "enqueue-files": queue(command, arg) elif command == "play-file": try: filename = URI(arg).filename except ValueError: filename = os.path.abspath(util.path.expanduser(arg)) queue("play-file", filename) elif command == "print-playing": try: queue("print-playing", args[0]) except IndexError: queue("print-playing") elif command == "print-query": queue(command, arg) elif command == "start-playing": actions.append(command) elif command == "no-plugins": actions.append(command) elif command == "run": actions.append(command) if cmds_todo: for cmd in cmds_todo: control(*cmd, **{"ignore_error": "run" in actions}) else: # this will exit if it succeeds control('focus', ignore_error=True) return actions, cmds_todo
def _init_gtk(): """Call before using Gtk/Gdk""" import gi # pygiaio 3.14rev16 switched to fontconfig for PangoCairo. As this results # in 100% CPU under win7 revert it. Maybe we need to update the # cache in the windows installer for it to work... but for now revert. if is_windows(): environ['PANGOCAIRO_BACKEND'] = 'win32' environ["GTK_CSD"] = "0" # disable for consistency and trigger events seem a bit flaky here if config.getboolean("settings", "scrollbar_always_visible"): environ["GTK_OVERLAY_SCROLLING"] = "0" try: # not sure if this is available under Windows gi.require_version("GdkX11", "3.0") from gi.repository import GdkX11 GdkX11 except (ValueError, ImportError): pass gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") gi.require_version("Pango", "1.0") gi.require_version('Soup', '2.4') from gi.repository import Gtk from quodlibet.qltk import ThemeOverrider, gtk_version # PyGObject doesn't fail anymore when init fails, so do it ourself initialized, argv[:] = Gtk.init_check(argv) if not initialized: raise SystemExit("Gtk.init failed") # include our own icon theme directory theme = Gtk.IconTheme.get_default() theme_search_path = get_image_dir() assert os.path.exists(theme_search_path) theme.append_search_path(theme_search_path) # Force menu/button image related settings. We might show too many atm # but this makes sure we don't miss cases where we forgot to force them # per widget. # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # some day... but not now warnings.filterwarnings( 'ignore', '.*Stock items are deprecated.*', Warning) warnings.filterwarnings( 'ignore', '.*:use-stock.*', Warning) warnings.filterwarnings( 'ignore', '.*The property GtkAlignment:[^\s]+ is deprecated.*', Warning) settings = Gtk.Settings.get_default() with warnings.catch_warnings(): warnings.simplefilter("ignore") settings.set_property("gtk-button-images", True) settings.set_property("gtk-menu-images", True) if hasattr(settings.props, "gtk_primary_button_warps_slider"): # https://bugzilla.gnome.org/show_bug.cgi?id=737843 settings.set_property("gtk-primary-button-warps-slider", True) # Make sure PyGObject includes support for foreign cairo structs try: gi.require_foreign("cairo") except ImportError: print_e("PyGObject is missing cairo support") exit(1) css_override = ThemeOverrider() if sys.platform == "darwin": # fix duplicated shadows for popups with Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" GtkWindow { box-shadow: none; } .tooltip { border-radius: 0; padding: 0; } .tooltip.background { background-clip: border-box; } """) css_override.register_provider("", style_provider) if gtk_version[:2] >= (3, 20): # https://bugzilla.gnome.org/show_bug.cgi?id=761435 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" spinbutton, button { min-height: 1.8rem; } .view button { min-height: 2.0rem; } entry { min-height: 2.4rem; } entry.cell { min-height: 0; } """) css_override.register_provider("Adwaita", style_provider) css_override.register_provider("HighContrast", style_provider) # https://github.com/quodlibet/quodlibet/issues/2541 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" treeview.view.separator { min-height: 2px; color: @borders; } """) css_override.register_provider("Ambiance", style_provider) css_override.register_provider("Radiance", style_provider) # https://github.com/quodlibet/quodlibet/issues/2677 css_override.register_provider("Clearlooks-Phenix", style_provider) if gtk_version[:2] >= (3, 18): # Hack to get some grab handle like thing for panes style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" GtkPaned.vertical, paned.vertical >separator { -gtk-icon-source: -gtk-icontheme("view-more-symbolic"); -gtk-icon-transform: rotate(90deg) scaleX(0.1) scaleY(3); } GtkPaned.horizontal, paned.horizontal >separator { -gtk-icon-source: -gtk-icontheme("view-more-symbolic"); -gtk-icon-transform: rotate(0deg) scaleX(0.1) scaleY(3); } """) css_override.register_provider("", style_provider) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # blacklist some modules, simply loading can cause segfaults sys.modules["gtk"] = None sys.modules["gpod"] = None sys.modules["gnome"] = None from quodlibet.qltk import pygobject_version, gtk_version MinVersions.GTK.check(gtk_version) MinVersions.PYGOBJECT.check(pygobject_version)
def _init_gtk(): """Call before using Gtk/Gdk""" import gi # pygiaio 3.14rev16 switched to fontconfig for PangoCairo. As this results # in 100% CPU under win7 revert it. Maybe we need to update the # cache in the windows installer for it to work... but for now revert. if is_windows(): os.environ['PANGOCAIRO_BACKEND'] = 'win32' os.environ["GTK_CSD"] = "0" # disable for consistency and trigger events seem a bit flaky here if is_osx(): os.environ["GTK_OVERLAY_SCROLLING"] = "0" # make sure GdkX11 doesn't get used under Windows if os.name == "nt": sys.modules["gi.repository.GdkX11"] = None try: # not sure if this is available under Windows gi.require_version("GdkX11", "3.0") from gi.repository import GdkX11 GdkX11 except (ValueError, ImportError): pass gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") gi.require_version("Pango", "1.0") gi.require_version('Soup', '2.4') from gi.repository import Gtk, Soup from quodlibet.qltk import ThemeOverrider, gtk_version # Work around missing annotation in older libsoup (Ubuntu 14.04 at least) message = Soup.Message() try: message.set_request(None, Soup.MemoryUse.COPY, b"") except TypeError: orig = Soup.Message.set_request def new_set_request(self, content_type, req_use, req_body): return orig(self, content_type, req_use, req_body, len(req_body)) Soup.Message.set_request = new_set_request # PyGObject doesn't fail anymore when init fails, so do it ourself initialized, argv = Gtk.init_check(sys.argv) if not initialized: raise SystemExit("Gtk.init failed") sys.argv = list(argv) # include our own icon theme directory theme = Gtk.IconTheme.get_default() theme_search_path = get_image_dir() assert os.path.exists(theme_search_path) theme.append_search_path(theme_search_path) # Force menu/button image related settings. We might show too many atm # but this makes sure we don't miss cases where we forgot to force them # per widget. # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # some day... but not now warnings.filterwarnings( 'ignore', '.*Stock items are deprecated.*', Warning) warnings.filterwarnings( 'ignore', '.*:use-stock.*', Warning) warnings.filterwarnings( 'ignore', '.*The property GtkAlignment:[^\s]+ is deprecated.*', Warning) settings = Gtk.Settings.get_default() with warnings.catch_warnings(): warnings.simplefilter("ignore") settings.set_property("gtk-button-images", True) settings.set_property("gtk-menu-images", True) if hasattr(settings.props, "gtk_primary_button_warps_slider"): # https://bugzilla.gnome.org/show_bug.cgi?id=737843 settings.set_property("gtk-primary-button-warps-slider", True) # Make sure PyGObject includes support for foreign cairo structs try: gi.require_foreign("cairo") except AttributeError: # older pygobject pass except ImportError: print_e("PyGObject is missing cairo support") exit(1) css_override = ThemeOverrider() # CSS overrides if os.name == "nt": # somehow borders are missing under Windows & Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" .menu { border: 1px solid @borders; } """) css_override.register_provider("", style_provider) if sys.platform == "darwin": # fix duplicated shadows for popups with Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" GtkWindow { box-shadow: none; } .tooltip { border-radius: 0; padding: 0; } .tooltip.background { background-clip: border-box; } """) css_override.register_provider("", style_provider) if gtk_version[:2] >= (3, 20): # https://bugzilla.gnome.org/show_bug.cgi?id=761435 style_provider = Gtk.CssProvider() style_provider.load_from_data(b""" spinbutton, button { min-height: 1.8rem; } .view button { min-height: 2.0rem; } entry { min-height: 2.4rem; } """) css_override.register_provider("Adwaita", style_provider) css_override.register_provider("HighContrast", style_provider) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # blacklist some modules, simply loading can cause segfaults sys.modules["gtk"] = None sys.modules["gpod"] = None sys.modules["gnome"] = None from quodlibet.qltk import pygobject_version, gtk_version MinVersions.GTK.check(gtk_version) MinVersions.PYGOBJECT.check(pygobject_version)
def test_any_object(self): with capture_output() as (stdout, stderr): print_e(42) assert u"42" in stderr.getvalue()
def test_basics(self): with capture_output() as (stdout, stderr): print_e(u"foo") assert u"foo" in stderr.getvalue()
def _init_gtk(): """Call before using Gtk/Gdk""" import gi # disable for consistency and trigger events seem a bit flaky here if is_osx(): os.environ["GTK_OVERLAY_SCROLLING"] = "0" # make sure GdkX11 doesn't get used under Windows if os.name == "nt": sys.modules["gi.repository.GdkX11"] = None try: # not sure if this is available under Windows gi.require_version("GdkX11", "3.0") from gi.repository import GdkX11 GdkX11 except (ValueError, ImportError): pass gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") gi.require_version("Pango", "1.0") from gi.repository import Gtk, Gdk # PyGObject doesn't fail anymore when init fails, so do it ourself initialized, argv = Gtk.init_check(sys.argv) if not initialized: raise SystemExit("Gtk.init failed") sys.argv = list(argv) # add Gtk.TreePath.__getitem__/__len__ for PyGObject 3.2 try: Gtk.TreePath()[0] except TypeError: Gtk.TreePath.__getitem__ = lambda self, index: list(self)[index] Gtk.TreePath.__len__ = lambda self: self.get_depth() # GTK+ 3.4+ constants if not hasattr(Gdk, "BUTTON_PRIMARY"): Gdk.BUTTON_PRIMARY = 1 Gdk.BUTTON_MIDDLE = 2 Gdk.BUTTON_SECONDARY = 3 if not hasattr(Gdk, "EVENT_PROPAGATE"): Gdk.EVENT_PROPAGATE = 0 Gdk.EVENT_STOP = 1 # include our own icon theme directory theme = Gtk.IconTheme.get_default() theme_search_path = get_image_dir() assert os.path.exists(theme_search_path) theme.append_search_path(theme_search_path) # Force menu/button image related settings. We might show too many atm # but this makes sure we don't miss cases where we forgot to force them # per widget. # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # some day... but not now warnings.filterwarnings('ignore', '.*Stock items are deprecated.*', Warning) warnings.filterwarnings('ignore', '.*:use-stock.*', Warning) warnings.filterwarnings( 'ignore', '.*The property GtkAlignment:[^\s]+ is deprecated.*', Warning) settings = Gtk.Settings.get_default() with warnings.catch_warnings(): warnings.simplefilter("ignore") settings.set_property("gtk-button-images", True) settings.set_property("gtk-menu-images", True) if hasattr(settings.props, "gtk_primary_button_warps_slider"): # https://bugzilla.gnome.org/show_bug.cgi?id=737843 settings.set_property("gtk-primary-button-warps-slider", True) # Make sure PyGObject includes support for foreign cairo structs try: gi.require_foreign("cairo") except AttributeError: # older pygobject pass except ImportError: print_e("PyGObject is missing cairo support") exit(1) # CSS overrides if os.name == "nt": # somehow borders are missing under Windows & Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(""" .menu { border: 1px solid @borders; } """) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) if sys.platform == "darwin": # fix duplicated shadows for popups with Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(""" GtkWindow { box-shadow: none; } .tooltip { border-radius: 0; padding: 0; } .tooltip.background { background-clip: border-box; } """) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # blacklist some modules, simply loading can cause segfaults sys.modules["gtk"] = None sys.modules["gpod"] = None sys.modules["gnome"] = None from quodlibet.qltk import pygobject_version, gtk_version MinVersions.GTK.check(gtk_version) MinVersions.PYGOBJECT.check(pygobject_version)
def _gtk_init(): """Call before using Gtk/Gdk""" import gi # make sure GdkX11 doesn't get used under Windows if os.name == "nt": sys.modules["gi.repository.GdkX11"] = None try: # not sure if this is available under Windows gi.require_version("GdkX11", "3.0") from gi.repository import GdkX11 GdkX11 except (ValueError, ImportError): pass gi.require_version("GLib", "2.0") gi.require_version("Gtk", "3.0") gi.require_version("Gdk", "3.0") gi.require_version("GObject", "2.0") gi.require_version("Pango", "1.0") gi.require_version("GdkPixbuf", "2.0") gi.require_version("Gio", "2.0") from gi.repository import Gtk, GObject, Gdk, GdkPixbuf # add Gtk.TreePath.__getitem__/__len__ for PyGObject 3.2 try: Gtk.TreePath()[0] except TypeError: Gtk.TreePath.__getitem__ = lambda self, index: list(self)[index] Gtk.TreePath.__len__ = lambda self: self.get_depth() # GTK+ 3.4+ constants if not hasattr(Gdk, "BUTTON_PRIMARY"): Gdk.BUTTON_PRIMARY = 1 Gdk.BUTTON_MIDDLE = 2 Gdk.BUTTON_SECONDARY = 3 if not hasattr(Gdk, "EVENT_PROPAGATE"): Gdk.EVENT_PROPAGATE = 0 Gdk.EVENT_STOP = 1 # On windows the default variants only do ANSI paths, so replace them. # In some typelibs they are replaced by default, in some don't.. if os.name == "nt": for name in ["new_from_file_at_scale", "new_from_file_at_size", "new_from_file"]: cls = GdkPixbuf.Pixbuf setattr(cls, name, getattr(cls, name + "_utf8", name)) # https://bugzilla.gnome.org/show_bug.cgi?id=670372 if not hasattr(GdkPixbuf.Pixbuf, "savev"): GdkPixbuf.Pixbuf.savev = GdkPixbuf.Pixbuf.save # Force menu/button image related settings. We might show too many atm # but this makes sure we don't miss cases where we forgot to force them # per widget. # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # some day... but not now warnings.filterwarnings( 'ignore', '.*Stock items are deprecated.*', Warning) warnings.filterwarnings( 'ignore', '.*:use-stock.*', Warning) warnings.filterwarnings( 'ignore', '.*The property GtkAlignment:[^\s]+ is deprecated.*', Warning) settings = Gtk.Settings.get_default() with warnings.catch_warnings(): warnings.simplefilter("ignore") settings.set_property("gtk-button-images", True) settings.set_property("gtk-menu-images", True) if hasattr(settings.props, "gtk_primary_button_warps_slider"): settings.set_property("gtk-primary-button-warps-slider", True) # Make sure PyGObject includes support for foreign cairo structs try: gi.require_foreign("cairo") except AttributeError: # older pygobject pass except ImportError: print_e("PyGObject is missing cairo support") exit(1) # CSS overrides if os.name == "nt": # somehow borders are missing under Windows & Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(""" .menu { border: 1px solid @borders; } """) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) if sys.platform == "darwin": # fix duplicated shadows for popups with Gtk+3.14 style_provider = Gtk.CssProvider() style_provider.load_from_data(""" GtkWindow { box-shadow: none; } .tooltip { border-radius: 0; padding: 0; } .tooltip.background { background-clip: border-box; } """) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) # https://bugzilla.gnome.org/show_bug.cgi?id=708676 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) # blacklist some modules, simply loading can cause segfaults sys.modules["gtk"] = None sys.modules["gpod"] = None sys.modules["glib"] = None sys.modules["gobject"] = None sys.modules["gnome"] = None from quodlibet.qltk import pygobject_version if pygobject_version < (3, 9): GObject.threads_init()
def process_arguments(argv): from quodlibet.util.uri import URI from quodlibet import util from quodlibet import const actions = [] controls = ["next", "previous", "play", "pause", "play-pause", "stop", "hide-window", "show-window", "toggle-window", "focus", "quit", "unfilter", "refresh", "force-previous"] controls_opt = ["seek", "order", "repeat", "query", "volume", "filter", "set-rating", "set-browser", "open-browser", "random", "song-list", "queue"] options = util.OptionParser( "Quod Libet", const.VERSION, _("a music library and player"), _("[option]")) options.add("print-playing", help=_("Print the playing song and exit")) options.add("start-playing", help=_("Begin playing immediately")) for opt, help in [ ("next", _("Jump to next song")), ("previous", _("Jump to previous song or restart if near the beginning")), ("force-previous", _("Jump to previous song")), ("play", _("Start playback")), ("pause", _("Pause playback")), ("play-pause", _("Toggle play/pause mode")), ("stop", _("Stop playback")), ("volume-up", _("Turn up volume")), ("volume-down", _("Turn down volume")), ("status", _("Print player status")), ("hide-window", _("Hide main window")), ("show-window", _("Show main window")), ("toggle-window", _("Toggle main window visibility")), ("focus", _("Focus the running player")), ("unfilter", _("Remove active browser filters")), ("refresh", _("Refresh and rescan library")), ("list-browsers", _("List available browsers")), ("print-playlist", _("Print the current playlist")), ("print-queue", _("Print the contents of the queue")), ("print-query-text", _("Print the active text query")), ("no-plugins", _("Start without plugins")), ("run", _("Start Quod Libet if it isn't running")), ("quit", _("Exit Quod Libet")), ]: options.add(opt, help=help) for opt, help, arg in [ ("seek", _("Seek within the playing song"), _("[+|-][HH:]MM:SS")), ("order", _("Set or toggle the playback order"), "[order]|toggle"), ("repeat", _("Turn repeat off, on, or toggle it"), "0|1|t"), ("volume", _("Set the volume"), "(+|-|)0..100"), ("query", _("Search your audio library"), _("query")), ("play-file", _("Play a file"), C_("command", "filename")), ("set-rating", _("Rate the playing song"), "0.0..1.0"), ("set-browser", _("Set the current browser"), "BrowserName"), ("open-browser", _("Open a new browser"), "BrowserName"), ("queue", _("Show or hide the queue"), "on|off|t"), ("song-list", _("Show or hide the main song list"), "on|off|t"), ("random", _("Filter on a random value"), C_("command", "tag")), ("filter", _("Filter on a tag value"), _("tag=value")), ("enqueue", _("Enqueue a file or query"), "%s|%s" % ( C_("command", "filename"), _("query"))), ("enqueue-files", _("Enqueue comma-separated files"), "%s[,%s..]" % ( _("filename"), _("filename"))), ("print-query", _("Print filenames of results of query to stdout"), _("query")), ("unqueue", _("Unqueue a file or query"), "%s|%s" % ( C_("command", "filename"), _("query"))), ]: options.add(opt, help=help, arg=arg) options.add("sm-config-prefix", arg="dummy") options.add("sm-client-id", arg="prefix") options.add("screen", arg="dummy") def is_vol(str): if str[0] in '+-': if len(str) == 1: return True str = str[1:] return str.isdigit() def is_time(str): if str[0] not in "+-0123456789": return False elif str[0] in "+-": str = str[1:] parts = str.split(":") if len(parts) > 3: return False else: return not (False in [p.isdigit() for p in parts]) def is_float(str): try: float(str) except ValueError: return False else: return True validators = { "order": ["0", "1", "t", "toggle", "inorder", "shuffle", "weighted", "onesong"].__contains__, "repeat": ["0", "1", "t", "on", "off", "toggle"].__contains__, "volume": is_vol, "seek": is_time, "set-rating": is_float, } cmds_todo = [] def queue(*args): cmds_todo.append(args) # XXX: to make startup work in case the desktop file isn't passed # a file path/uri if argv[-1] == "--play-file": argv = argv[:-1] opts, args = options.parse(argv[1:]) for command, arg in opts.items(): if command in controls: queue(command) elif command in controls_opt: if command in validators and not validators[command](arg): print_e(_("Invalid argument for '%s'.") % command) print_e(_("Try %s --help.") % sys.argv[0]) exit_(True, notify_startup=True) else: queue(command, arg) elif command == "status": queue("status") elif command == "print-playlist": queue("dump-playlist") elif command == "print-queue": queue("dump-queue") elif command == "list-browsers": queue("dump-browsers") elif command == "volume-up": queue("volume +") elif command == "volume-down": queue("volume -") elif command == "enqueue" or command == "unqueue": try: filename = URI(arg).filename except ValueError: filename = arg queue(command, filename) elif command == "enqueue-files": queue(command, arg) elif command == "play-file": try: filename = URI(arg).filename except ValueError: filename = os.path.abspath(util.path.expanduser(arg)) queue("play-file", filename) elif command == "print-playing": try: queue("print-playing", args[0]) except IndexError: queue("print-playing") elif command == "print-query": queue(command, arg) elif command == "print-query-text": queue(command) elif command == "start-playing": actions.append(command) elif command == "no-plugins": actions.append(command) elif command == "run": actions.append(command) if cmds_todo: for cmd in cmds_todo: control(*cmd, **{"ignore_error": "run" in actions}) else: # this will exit if it succeeds control('focus', ignore_error=True) return actions, cmds_todo