File: main.py

package info (click to toggle)
mypaint 2.0.1-14
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,884 kB
  • sloc: python: 43,893; cpp: 6,931; xml: 2,475; sh: 473; makefile: 25
file content (280 lines) | stat: -rw-r--r-- 10,009 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# This file is part of MyPaint.
# Copyright (C) 2007-2013 by Martin Renold <martinxyz@gmx.ch>
# Copyright (C) 2013-2018 by the MyPaint Development Team.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

"""Command-line handling - traditional main() function."""

## Imports *nothing involving mypaintlib at this point*

from __future__ import division, print_function

import os
import sys
import logging
import warnings

from lib.gibindings import GdkPixbuf
from optparse import OptionParser

import lib.config
import lib.glib
from lib.i18n import USER_LOCALE_PREF
from lib.meta import MYPAINT_VERSION
import gui.userconfig

logger = logging.getLogger(__name__)


## Method defs

def _init_gtk_workarounds():
    """Initialize some workarounds for unoptimal GTK behavior"""
    # Via https://code.google.com/p/quodlibet/source/browse/quodlibet/
    logger.debug("Adding GTK workarounds...")

    # 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
            func = getattr(cls, name + "_utf8", None)
            if func:
                logger.debug(
                    "Monkeypatching GdkPixbuf.Pixbuf.%s with %r",
                    name, func,
                )
                setattr(cls, name, func)

    # Wayland "workaround" to avoid input freeze on pointer grabs.
    # Respect existing envvars for testing (and general courtesy).
    # This relies on XWayland being available.
    if sys.platform.startswith("linux") and 'GDK_BACKEND' not in os.environ:
        os.environ['GDK_BACKEND'] = 'x11'

    logger.debug("GTK workarounds added.")


def set_user_configured_locale(userconfpath):
    """If configured, set envvars for a custom locale
    The user can choose to not use their system locale
    by explicitly choosing another, and restarting.
    """
    settings = gui.userconfig.get_json_config(userconfpath)
    if USER_LOCALE_PREF in settings:
        user_locale = settings[USER_LOCALE_PREF]
        supported = ["en", "en_US"] + lib.config.supported_locales
        if user_locale not in supported:
            logger.warning("Locale not supported: %s", user_locale)
        else:
            # GIMP, Krita and Inkscape will all ignore the LANGUAGE
            # variable when a user has chosen a language explicitly.
            # For better or worse, we follow their example
            # (this may change in the future).
            logger.info("Using locale: (%s)", user_locale)
            if "LANGUAGE" in os.environ:
                logger.warning("LANGUAGE envvar is overridden by user setting")
            os.environ["LANGUAGE"] = user_locale
    else:
        logger.info("No locale setting found, using system locale")


def main(
        datapath,
        iconspath,
        localepath,
        oldstyle_confpath=None,
        version=MYPAINT_VERSION,
        debug=False
):
    """Run MyPaint with `sys.argv_unicode`, called from the "mypaint" script.

    :param unicode datapath: The app's read-only data location.
    :param unicode iconspath: Extra search root for finding icons.
    :param unicode oldstyle_confpath: Old-style merged config folder.
    :param unicode version: full version string for the about box.
    :param bool debug: whether debug functionality and logging is enabled.

    The ``datapath`` parameter defines where MyPaint should find its
    static data, e.g. UI definition XML, backgrounds, and brush
    definitions. $PREFIX/share/mypaint is usual.

    The iconspath`` parameter tells MyPaint where to find its themeable UI
    icons. This will be used in addition to $XDG_DATA_DIRS for the
    purposes of icon lookup.  Normally it's $PREFIX/share/icons.

    If specified oldstyle_confpath must be a single directory path.  all
    user-specific data that MyPaint writes is written here.  If omitted,
    this data will be stored under the basedirs returned by
    GLib.get_user_config_dir() for settings, and by
    GLib.get_user_data_dir() for brushes and backgrounds. On Windows,
    these will be the same location.  On POSIX systems,
    $HOME/.config/mypaint and $HOME/.local/share/mypaint are a typical
    division.

    See `lib.meta` for details of what normally goes in `version`.

    """

    # Init the workaround before we start importing anything which
    # could still be using gtk2compat.
    _init_gtk_workarounds()

    # GLib user dirs: cache them now for greatest compatibility.
    # Importing mypaintlib before the 1st call to g_get_user*_dir()
    # breaks GLib for obscure reasons.
    # This needs to be done after i18n setup, or Windows configurations
    # with non-ASCII character in %USERPROFILE% will break.
    lib.glib.init_user_dir_caches()

    options, args = parsed_cmdline_arguments(oldstyle_confpath, debug)

    # XDG support for new users on POSIX platforms
    if options.config is None:
        appsubdir = u"mypaint"
        basedir = lib.glib.get_user_data_dir()
        userdatapath = os.path.join(basedir, appsubdir)
        basedir = lib.glib.get_user_config_dir()
        userconfpath = os.path.join(basedir, appsubdir)
    else:
        userdatapath = options.config
        userconfpath = options.config

    # Log to the specified location
    # Prepend the data path if the user gave a relative location.
    if options.logfile:
        logfilepath = os.path.join(userdatapath, options.logfile)
        logdirpath, logfilebasename = os.path.split(logfilepath)
        if not os.path.isdir(logdirpath):
            os.makedirs(logdirpath)
        logger.info("Copying log messages to %r", logfilepath)
        logfile_handler = logging.FileHandler(
            logfilepath, mode="a",
            encoding="utf-8",
        )
        logfile_format = "%(asctime)s;%(levelname)s;%(name)s;%(message)s"
        logfile_handler.setFormatter(logging.Formatter(logfile_format))
        root_logger = logging.getLogger(None)
        root_logger.addHandler(logfile_handler)

    if debug:
        logger.critical("Test critical message, please ignore")
        warnings.resetwarnings()
        logging.captureWarnings(True)

    if options.version:
        # Output (rather than log) the version
        print("MyPaint version %s" % (version, ))
        sys.exit(0)

    def run():
        logger.debug('user_datapath: %r', userdatapath)
        logger.debug('user_confpath: %r', userconfpath)

        # User-configured locale (if enabled by user)
        set_user_configured_locale(userconfpath)

        # Locale setting
        from lib.gettext_setup import init_gettext
        init_gettext(localepath)

        # mypaintlib import is performed first in gui.application now.
        from gui import application

        app_state_dirs = application.StateDirs(
            app_data = datapath,
            app_icons = iconspath,
            user_data = userdatapath,
            user_config = userconfpath,
        )
        app = application.Application(
            filenames = args,
            state_dirs = app_state_dirs,
            version = version,
            fullscreen = options.fullscreen,
        )

        # Gtk must not be imported before init_gettext
        # has been run - else locales will not be set
        # up properly (e.g: left-to-right interfaces for right-to-left scripts)
        # Note that this is not the first import of Gtk in the __program__;
        # it is imported indirectly via the import of gui.application
        from lib.gibindings import Gtk
        settings = Gtk.Settings.get_default()
        dark = app.preferences.get("ui.dark_theme_variant", True)
        settings.set_property("gtk-application-prefer-dark-theme", dark)

        if debug and options.run_and_quit:
            from lib.gibindings import GLib
            GLib.timeout_add(1000, lambda *a: Gtk.main_quit())
        else:
            from gui import gtkexcepthook
            func = app.filehandler.confirm_destructive_action
            gtkexcepthook.quit_confirmation_func = func

        # temporary workaround for gtk3 Ctrl-C bug:
        # https://bugzilla.gnome.org/show_bug.cgi?id=622084
        import signal
        signal.signal(signal.SIGINT, signal.SIG_DFL)
        Gtk.main()

    if options.trace:
        import trace
        tracer = trace.Trace(trace=1, count=0)
        tracer.runfunc(run)
    else:
        run()


def parsed_cmdline_arguments(default_confpath, debug=False):
    """Parse command line arguments and return result

    :return: (options, positional arguments)
    """
    parser = OptionParser('usage: %prog [options] [FILE]')
    parser.add_option(
        '-c',
        '--config',
        metavar='DIR',
        default=default_confpath,
        help='use old-style merged config directory DIR, e.g. ~/.mypaint'
    )
    parser.add_option(
        '-l',
        '--logfile',
        metavar='FILE',
        default=None,
        help='log console messages to FILE (rel. to config location)'
    )
    parser.add_option(
        '-t',
        '--trace',
        action="store_true",
        help='print all executed Python statements'
    )
    parser.add_option(
        '-f',
        '--fullscreen',
        action="store_true",
        help='start in fullscreen mode'
    )
    parser.add_option(
        "-V",
        '--version',
        action="store_true",
        help='print version information and exit'
    )
    if debug:
        parser.add_option(
            "-R",
            '--run-and-quit',
            action="store_true",
            help='start the program and shut it down after 1 second'
        )

    return parser.parse_args(sys.argv_unicode[1:])