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