1# Copyright 2012 Christoph Reiter 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7 8import os 9import sys 10import warnings 11import logging 12 13from senf import environ, argv, fsn2text 14 15from quodlibet.const import MinVersions 16from quodlibet import config 17from quodlibet.util import is_osx, is_windows, i18n 18from quodlibet.util.dprint import print_e, PrintHandler 19from quodlibet.util.urllib import install_urllib2_ca_file 20 21from ._main import get_base_dir, is_release, get_image_dir, get_cache_dir 22 23 24_cli_initialized = False 25_initialized = False 26 27 28def _init_gtk_debug(no_excepthook): 29 from quodlibet.errorreport import enable_errorhook 30 31 enable_errorhook(not no_excepthook) 32 33 34def is_init(): 35 """Returns if init() was called""" 36 37 global _initialized 38 39 return _initialized 40 41 42def init(no_translations=False, no_excepthook=False, config_file=None): 43 """This needs to be called before any API can be used. 44 Might raise in case of an error. 45 46 Pass no_translations=True to disable translations (used by tests) 47 """ 48 49 global _initialized 50 51 if _initialized: 52 return 53 54 init_cli(no_translations=no_translations, config_file=config_file) 55 _init_gtk() 56 _init_gtk_debug(no_excepthook=no_excepthook) 57 _init_gst() 58 _init_dbus() 59 60 _initialized = True 61 62 63def _init_gettext(no_translations=False): 64 """Call before using gettext helpers""" 65 66 if no_translations: 67 language = u"C" 68 else: 69 language = config.gettext("settings", "language") 70 if not language: 71 language = None 72 73 i18n.init(language) 74 75 # Use the locale dir in ../build/share/locale if there is one 76 localedir = os.path.join( 77 os.path.dirname(get_base_dir()), "build", "share", "locale") 78 if not os.path.isdir(localedir): 79 localedir = None 80 81 i18n.register_translation("quodlibet", localedir) 82 debug_text = environ.get("QUODLIBET_TEST_TRANS") 83 if debug_text is not None: 84 i18n.set_debug_text(fsn2text(debug_text)) 85 86 87def _init_python(): 88 MinVersions.PYTHON3.check(sys.version_info) 89 90 if is_osx(): 91 # We build our own openssl on OSX and need to make sure that 92 # our own ca file is used in all cases as the non-system openssl 93 # doesn't use the system certs 94 install_urllib2_ca_file() 95 96 if is_windows(): 97 # Not really needed on Windows as pygi-aio seems to work fine, but 98 # wine doesn't have certs which we use for testing. 99 install_urllib2_ca_file() 100 101 if is_windows() and os.sep != "\\": 102 # In the MSYS2 console MSYSTEM is set, which breaks os.sep/os.path.sep 103 # If you hit this do a "setup.py clean -all" to get rid of the 104 # bytecode cache then start things with "MSYSTEM= ..." 105 raise AssertionError("MSYSTEM is set (%r)" % environ.get("MSYSTEM")) 106 107 logging.getLogger().addHandler(PrintHandler()) 108 109 110def _init_formats(): 111 from quodlibet.formats import init 112 init() 113 114 115def init_cli(no_translations=False, config_file=None): 116 """This needs to be called before any API can be used. 117 Might raise in case of an error. 118 119 Like init() but for code not using Gtk etc. 120 """ 121 122 global _cli_initialized 123 124 if _cli_initialized: 125 return 126 127 _init_python() 128 config.init_defaults() 129 if config_file is not None: 130 config.init(config_file) 131 _init_gettext(no_translations) 132 _init_formats() 133 _init_g() 134 135 _cli_initialized = True 136 137 138def _init_dbus(): 139 """Setup dbus mainloop integration. Call before using dbus""" 140 141 # To make GDBus fail early and we don't have to wait for a timeout 142 if is_osx() or is_windows(): 143 os.environ["DBUS_SYSTEM_BUS_ADDRESS"] = "something-invalid" 144 os.environ["DBUS_SESSION_BUS_ADDRESS"] = "something-invalid" 145 146 try: 147 from dbus.mainloop.glib import DBusGMainLoop, threads_init 148 except ImportError: 149 try: 150 import dbus.glib 151 dbus.glib 152 except ImportError: 153 return 154 else: 155 threads_init() 156 DBusGMainLoop(set_as_default=True) 157 158 159def _fix_gst_leaks(): 160 """gst_element_add_pad and gst_bin_add are wrongly annotated and lead 161 to PyGObject refing the passed element. 162 163 Work around by adding a wrapper that unrefs afterwards. 164 Can be called multiple times. 165 166 https://bugzilla.gnome.org/show_bug.cgi?id=741390 167 https://bugzilla.gnome.org/show_bug.cgi?id=702960 168 """ 169 170 from gi.repository import Gst 171 172 assert Gst.is_initialized() 173 174 def do_wrap(func): 175 def wrap(self, obj): 176 result = func(self, obj) 177 obj.unref() 178 return result 179 return wrap 180 181 parent = Gst.Bin() 182 elm = Gst.Bin() 183 parent.add(elm) 184 if elm.__grefcount__ == 3: 185 elm.unref() 186 Gst.Bin.add = do_wrap(Gst.Bin.add) 187 188 pad = Gst.Pad.new("foo", Gst.PadDirection.SRC) 189 parent.add_pad(pad) 190 if pad.__grefcount__ == 3: 191 pad.unref() 192 Gst.Element.add_pad = do_wrap(Gst.Element.add_pad) 193 194 195def _init_g(): 196 """Call before using GdkPixbuf/GLib/Gio/GObject""" 197 198 import gi 199 200 gi.require_version("GLib", "2.0") 201 gi.require_version("Gio", "2.0") 202 gi.require_version("GObject", "2.0") 203 gi.require_version("GdkPixbuf", "2.0") 204 205 # Newer glib is noisy regarding deprecated signals/properties 206 # even with stable releases. 207 if is_release(): 208 warnings.filterwarnings( 209 'ignore', '.* It will be removed in a future version.', 210 Warning) 211 212 # blacklist some modules, simply loading can cause segfaults 213 sys.modules["glib"] = None 214 sys.modules["gobject"] = None 215 216 217def _init_gtk(): 218 """Call before using Gtk/Gdk""" 219 220 import gi 221 222 if config.getboolean("settings", "pangocairo_force_fontconfig") and \ 223 "PANGOCAIRO_BACKEND" not in environ: 224 environ["PANGOCAIRO_BACKEND"] = "fontconfig" 225 226 # disable for consistency and trigger events seem a bit flaky here 227 if config.getboolean("settings", "scrollbar_always_visible"): 228 environ["GTK_OVERLAY_SCROLLING"] = "0" 229 230 try: 231 # not sure if this is available under Windows 232 gi.require_version("GdkX11", "3.0") 233 from gi.repository import GdkX11 234 GdkX11 235 except (ValueError, ImportError): 236 pass 237 238 gi.require_version("Gtk", "3.0") 239 gi.require_version("Gdk", "3.0") 240 gi.require_version("Pango", "1.0") 241 gi.require_version('Soup', '2.4') 242 gi.require_version('PangoCairo', "1.0") 243 244 from gi.repository import Gtk 245 from quodlibet.qltk import ThemeOverrider, gtk_version 246 247 # PyGObject doesn't fail anymore when init fails, so do it ourself 248 initialized, argv[:] = Gtk.init_check(argv) 249 if not initialized: 250 raise SystemExit("Gtk.init failed") 251 252 # include our own icon theme directory 253 theme = Gtk.IconTheme.get_default() 254 theme_search_path = get_image_dir() 255 assert os.path.exists(theme_search_path) 256 theme.append_search_path(theme_search_path) 257 258 # Force menu/button image related settings. We might show too many atm 259 # but this makes sure we don't miss cases where we forgot to force them 260 # per widget. 261 # https://bugzilla.gnome.org/show_bug.cgi?id=708676 262 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) 263 264 # some day... but not now 265 warnings.filterwarnings( 266 'ignore', '.*Stock items are deprecated.*', Warning) 267 warnings.filterwarnings( 268 'ignore', '.*:use-stock.*', Warning) 269 warnings.filterwarnings( 270 'ignore', r'.*The property GtkAlignment:[^\s]+ is deprecated.*', 271 Warning) 272 273 settings = Gtk.Settings.get_default() 274 with warnings.catch_warnings(): 275 warnings.simplefilter("ignore") 276 settings.set_property("gtk-button-images", True) 277 settings.set_property("gtk-menu-images", True) 278 if hasattr(settings.props, "gtk_primary_button_warps_slider"): 279 # https://bugzilla.gnome.org/show_bug.cgi?id=737843 280 settings.set_property("gtk-primary-button-warps-slider", True) 281 282 # Make sure PyGObject includes support for foreign cairo structs 283 try: 284 gi.require_foreign("cairo") 285 except ImportError: 286 print_e("PyGObject is missing cairo support") 287 exit(1) 288 289 css_override = ThemeOverrider() 290 291 if sys.platform == "darwin": 292 # fix duplicated shadows for popups with Gtk+3.14 293 style_provider = Gtk.CssProvider() 294 style_provider.load_from_data(b""" 295 GtkWindow { 296 box-shadow: none; 297 } 298 .tooltip { 299 border-radius: 0; 300 padding: 0; 301 } 302 .tooltip.background { 303 background-clip: border-box; 304 } 305 """) 306 css_override.register_provider("", style_provider) 307 308 if gtk_version[:2] >= (3, 20): 309 # https://bugzilla.gnome.org/show_bug.cgi?id=761435 310 style_provider = Gtk.CssProvider() 311 style_provider.load_from_data(b""" 312 spinbutton, button { 313 min-height: 22px; 314 } 315 316 .view button { 317 min-height: 24px; 318 } 319 320 entry { 321 min-height: 28px; 322 } 323 324 entry.cell { 325 min-height: 0; 326 } 327 """) 328 css_override.register_provider("Adwaita", style_provider) 329 css_override.register_provider("HighContrast", style_provider) 330 331 # https://github.com/quodlibet/quodlibet/issues/2541 332 style_provider = Gtk.CssProvider() 333 style_provider.load_from_data(b""" 334 treeview.view.separator { 335 min-height: 2px; 336 color: @borders; 337 } 338 """) 339 css_override.register_provider("Ambiance", style_provider) 340 css_override.register_provider("Radiance", style_provider) 341 # https://github.com/quodlibet/quodlibet/issues/2677 342 css_override.register_provider("Clearlooks-Phenix", style_provider) 343 # https://github.com/quodlibet/quodlibet/issues/2997 344 css_override.register_provider("Breeze", style_provider) 345 346 if gtk_version[:2] >= (3, 18): 347 # Hack to get some grab handle like thing for panes 348 style_provider = Gtk.CssProvider() 349 style_provider.load_from_data(b""" 350 GtkPaned.vertical, paned.vertical >separator { 351 -gtk-icon-source: -gtk-icontheme("view-more-symbolic"); 352 -gtk-icon-transform: rotate(90deg) scaleX(0.1) scaleY(3); 353 } 354 355 GtkPaned.horizontal, paned.horizontal >separator { 356 -gtk-icon-source: -gtk-icontheme("view-more-symbolic"); 357 -gtk-icon-transform: rotate(0deg) scaleX(0.1) scaleY(3); 358 } 359 """) 360 css_override.register_provider("", style_provider) 361 362 # https://bugzilla.gnome.org/show_bug.cgi?id=708676 363 warnings.filterwarnings('ignore', '.*g_value_get_int.*', Warning) 364 365 # blacklist some modules, simply loading can cause segfaults 366 sys.modules["gtk"] = None 367 sys.modules["gpod"] = None 368 sys.modules["gnome"] = None 369 370 from quodlibet.qltk import pygobject_version, gtk_version 371 372 MinVersions.GTK.check(gtk_version) 373 MinVersions.PYGOBJECT.check(pygobject_version) 374 375 376def _init_gst(): 377 """Call once before importing GStreamer""" 378 379 arch_key = "64" if sys.maxsize > 2**32 else "32" 380 registry_name = "gst-registry-%s-%s.bin" % (sys.platform, arch_key) 381 environ["GST_REGISTRY"] = os.path.join(get_cache_dir(), registry_name) 382 383 assert "gi.repository.Gst" not in sys.modules 384 385 import gi 386 387 # We don't want python-gst, it changes API.. 388 assert "gi.overrides.Gst" not in sys.modules 389 sys.modules["gi.overrides.Gst"] = None 390 391 # blacklist some modules, simply loading can cause segfaults 392 sys.modules["gst"] = None 393 394 # We don't depend on Gst overrides, so make sure it's initialized. 395 try: 396 gi.require_version("Gst", "1.0") 397 from gi.repository import Gst 398 except (ValueError, ImportError): 399 return 400 401 if Gst.is_initialized(): 402 return 403 404 from gi.repository import GLib 405 406 try: 407 ok, argv[:] = Gst.init_check(argv) 408 except GLib.GError: 409 print_e("Failed to initialize GStreamer") 410 # Uninited Gst segfaults: make sure no one can use it 411 sys.modules["gi.repository.Gst"] = None 412 else: 413 # monkey patching ahead 414 _fix_gst_leaks() 415