1#!/usr/bin/env python3 2# Copyright 2006-2009 Scott Horowitz <stonecrest@gmail.com> 3# Copyright 2009-2014 Jonathan Ballet <jon@multani.info> 4# 5# This file is part of Sonata. 6# 7# Sonata is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# Sonata is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with Sonata. If not, see <http://www.gnu.org/licenses/>. 19 20"""Sonata is a simple GTK+ client for the Music Player Daemon.""" 21 22import sys 23if sys.version_info <= (3, 2): 24 sys.stderr.write("Sonata requires Python 3.2+\n") 25 sys.exit(1) 26 27import gettext 28import locale 29import logging 30import os 31import platform 32import threading # needed for interactive shell 33import gi 34 35def run(): 36 """Main entry point of Sonata""" 37 38 # TODO: allow to exit the application with Ctrl+C from the terminal 39 # This is a fix for https://bugzilla.gnome.org/show_bug.cgi?id=622084 40 import signal 41 signal.signal(signal.SIGINT, signal.SIG_DFL) 42 43 # XXX insert the correct sonata package dir in sys.path 44 45 logging.basicConfig( 46 level=logging.WARNING, 47 format="[%(asctime)s] [%(threadName)s] %(name)s: %(message)s", 48 datefmt="%Y-%m-%d %H:%M:%S", 49 stream=sys.stderr) 50 51 logger = logging.getLogger(__name__) 52 53 try: 54 import sonata 55 except ImportError: 56 logger.critical("Python failed to find the sonata modules.") 57 logger.critical("Searched in the following directories:\n%s", 58 "\n".join(sys.path)) 59 logger.critical("Perhaps Sonata is improperly installed?") 60 sys.exit(1) 61 62 try: 63 from sonata.version import version 64 except ImportError: 65 logger.critical("Python failed to find the sonata modules.") 66 logger.critical("An old or incomplete installation was " 67 "found in the following directory: %s", 68 os.path.dirname(sonata.__file__)) 69 logger.critical("Perhaps you want to delete it?") 70 sys.exit(1) 71 72 # XXX check that version.VERSION is what this script was installed for 73 74 ## Apply global fixes: 75 76 if platform.system() == 'Linux': 77 sys.argv[0] = "sonata" 78 import ctypes 79 libc = ctypes.CDLL('libc.so.6') 80 PR_SET_NAME = 15 81 libc.prctl(PR_SET_NAME, b"sonata", 0, 0, 0) 82 83 ## Apply locale and translation: 84 # Try to find a "good" locale directory. 85 for path in [ 86 # This is useful when working from the source repository 87 os.path.join(os.path.dirname(sonata.__file__), "share", "locale"), 88 # This is useful when Sonata is installed in a special place 89 os.path.join(sonata.__file__.split('/lib')[0], 'share', 'locale'), 90 ]: 91 if os.path.exists(path): 92 locales_path = path 93 break 94 else: 95 # This tells gettext to look at the default place for the translation 96 # files. 97 locales_path = None 98 99 # Gtk.Builder uses gettext functions from C library. Enable 100 # correct localization for these functions with the locale 101 # module. See: 102 # https://docs.python.org/3/library/locale.html#access-to-message-catalogs 103 try: 104 locale.setlocale(locale.LC_ALL, '') 105 except locale.Error as e: 106 # If locale is not supported by C library, the initial call to 107 # locale.setlocale will fail and raise an exception. Any Glade 108 # strings would not be translated. But native python strings 109 # would still be translated if the .mo files are included in 110 # sonata! 111 # 112 # To prevent a mix of languages, disable translation for both: 113 # 1. explicitly set locale to 'C' (default strings) 114 # 2. don't provide python gettext with any translatable 115 # strings (localedir=None), but still install the required 116 # _() function 117 logger.error("setlocale() failed: %s. Falling back to default locale.", e) 118 locale.setlocale(locale.LC_ALL, 'C') 119 gettext.install(True, localedir=None, names=["ngettext"]) 120 else: 121 gettext.install('sonata', locales_path, names=["ngettext"]) 122 123 # bindtextdomain() is GNU libc specific and may not be available 124 # on other systems (e.g. OSX) 125 if hasattr(locale, 'bindtextdomain'): 126 locale.bindtextdomain('sonata', locales_path) 127 128 gettext.textdomain('sonata') 129 gettext.bindtextdomain('sonata', locales_path) 130 131 132 ## Check initial dependencies: 133 try: 134 import mpd 135 except: 136 logger.critical("Sonata requires python-mpd2. Aborting...") 137 sys.exit(1) 138 139 140 ## Initialize the plugin system: 141 142 from sonata.pluginsystem import pluginsystem 143 pluginsystem.find_plugins() 144 pluginsystem.notify_of('enablables', 145 lambda plugin, cb: cb(True), 146 lambda plugin, cb: cb(False)) 147 148 149 ## Load the command line interface: 150 151 from sonata import cli 152 args = cli.Args() 153 args.parse(sys.argv) 154 155 ## Deal with GTK: 156 157 if not args.skip_gui: 158 # importing gtk does sys.setdefaultencoding("utf-8"), sets locale etc. 159 gi.require_version('Gtk', '3.0') 160 from gi.repository import Gtk, Gdk 161 else: 162 class FakeModule: 163 pass 164 # make sure the ui modules aren't imported 165 for m in 'gtk', 'pango', 'sonata.ui', 'sonata.breadcrumbs': 166 if m in sys.modules: 167 logger.warning( 168 "Module %s imported in CLI mode (it should not)", m) 169 else: 170 sys.modules[m] = FakeModule() 171 172 173 ## Global init: 174 175 from socket import setdefaulttimeout as socketsettimeout 176 socketsettimeout(5) 177 178 if not args.skip_gui: 179 Gdk.threads_init() 180 181 ## CLI actions: 182 183 args.execute_cmds() 184 185 ## Load the main application: 186 187 from sonata import main 188 189 190 def on_application_activate(application): 191 Gdk.threads_enter() 192 windows = application.get_windows() 193 194 if windows: 195 for window in windows: 196 window.present() 197 else: 198 sonata = main.Base(args) 199 sonata.window.set_application(application) 200 Gdk.threads_leave() 201 202 app = Gtk.Application(application_id="org.MPD.Sonata") 203 app.connect("activate", on_application_activate) 204 205 ## Load the shell 206 # yo dawg, I heard you like python, 207 # so I put a python shell in your python application 208 # so you can debug while you run it. 209 if args.start_shell: 210 # the enviroment used for the shell 211 scope = dict(list(globals().items()) + list(locals().items())) 212 def run_shell(): 213 try: 214 import IPython 215 IPython.embed(user_ns=scope) 216 except ImportError as e: # fallback if ipython is not avaible 217 import code 218 shell = code.InteractiveConsole(scope) 219 shell.interact() 220 # quit program if shell is closed, 221 # This is the only way to close the program clone in this mode, 222 # because we can't close the shell thread easily 223 from gi.repository import Gtk 224 Gtk.main_quit() 225 threading.Thread(target=run_shell, name="Shell").start() 226 227 try: 228 app.run([]) 229 except KeyboardInterrupt: 230 Gtk.main_quit() 231