diff --git a/src/metro/__init__.py b/src/metro/__init__.py index 970a1aa..22d96fc 100755 --- a/src/metro/__init__.py +++ b/src/metro/__init__.py @@ -55,85 +55,43 @@ def parse_args(prog_name, cli_hook=None): return cli.parse_known_args() -def init(args, window_title, local_path='~/.metro', - profile_path='~/.metro/profiles'): - import sys +load_GUI = None - if args.core_mode: - def die(msg): - print('Fatal error during initialization: ' + msg) - sys.exit(0) - else: - def die(msg): - if sys.version_info[0] == 2: - import Tkinter # different name in python2 - tkinter = Tkinter - else: - import tkinter - - root = tkinter.Tk() - root.wm_title(window_title) - - frame = tkinter.Frame(borderwidth=5) - label = tkinter.Label(frame, justify=tkinter.LEFT, wraplength=450, - text='Fatal error during ' - 'initialization:\n\n' + msg) - label.grid(padx=5, pady=5) +def init_core(): + global load_GUI + if load_GUI is None: + load_GUI = False # initialize in core mode + elif not load_GUI: + return # already initialized in core mode - button = tkinter.Button(frame, text='Close', - command=lambda: root.quit()) - button.grid(pady=5) - - frame.grid() - frame.mainloop() + import sys + if not load_GUI: + def die(msg): + print('Fatal error during initialization: ' + msg) sys.exit(0) + globals().update({'die': die}) if sys.version_info[:2] < (3, 3): - die('Requires python version >= 3.3 (found {0})'.format( - sys.version[:sys.version.find(' ')] - )) + globals()['die']('Requires python version >= 3.3 (found {0})'.format( + sys.version[:sys.version.find(' ')])) try: import typing # noqa (F401) import numpy # noqa (F401) from PyQt5 import QtCore # noqa (F401) - - if not args.core_mode: - from PyQt5 import QtGui # noqa (F401) - from PyQt5 import QtWidgets # noqa (F401) except ImportError as e: - die('An essential dependency ({0}) could not be imported and is ' - 'probably missing'.format(str(e)[str(e)[:-1].rfind('\'')+1:-1])) + globals()['die']('An essential dependency ({0}) could not be ' + 'imported and is probably missing'.format( + str(e)[str(e)[:-1].rfind('\'')+1:-1])) # Populate the metro namespace with a variety of internal modules # and parts of Qt. In core mode, several of those are simulated by # constructed module objects to allow the definition of related # classes without any actual dependency. - import os - import pkg_resources - - local_path = os.path.expanduser(local_path) - os.makedirs(local_path, exist_ok=True) - - profile_path = os.path.expanduser(profile_path) - os.makedirs(profile_path, exist_ok=True) - globals().update({ - 'WINDOW_TITLE': window_title, - 'LOCAL_PATH': local_path, - 'PROFILE_PATH': profile_path, - 'resource_exists': pkg_resources.resource_exists, - 'resource_filename': pkg_resources.resource_filename, - 'die': die - }) - - globals().update({ - 'core_mode': args.core_mode, - 'kiosk_mode': args.kiosk_mode, - 'QtCore': QtCore, 'QObject': QtCore.QObject, 'QSignal': QtCore.pyqtSignal, @@ -141,26 +99,23 @@ def die(msg): 'QProperty': QtCore.pyqtProperty, 'QTimer': QtCore.QTimer, 'QThread': QtCore.QThread, - 'QConsts': QtCore.Qt, + 'QConsts': QtCore.Qt }) - if args.core_mode: + if not load_GUI: class EmptyQtModule: def __getattr__(self, name): return QtCore.QObject QtGui = EmptyQtModule() # noqa QtWidgets = EmptyQtModule() # noqa - QtUic = EmptyQtModule() - - else: - from PyQt5 import uic as QtUic + QtUic = EmptyQtModule() # noqa - globals().update({ - 'QtGui': QtGui, - 'QtWidgets': QtWidgets, - 'QtUic': QtUic - }) + globals().update({ + 'QtGui': QtGui, + 'QtWidgets': QtWidgets, + 'QtUic': QtUic + }) from .services import channels globals().update({ @@ -226,28 +181,100 @@ def __getattr__(self, name): }) -def init_mp_support(): +def init_gui(): + global load_GUI + if load_GUI is None: + load_GUI = True # initialize GUI modules + elif load_GUI: + return # already initialized GUI modules + + import sys + + def die(msg): + if sys.version_info[0] == 2: + import Tkinter # different name in python2 + tkinter = Tkinter + else: + import tkinter + + root = tkinter.Tk() + try: + window_title = globals()['WINDOW_TITLE'] + except KeyError: + window_title = 'Metro' + root.wm_title(window_title) + + frame = tkinter.Frame(borderwidth=5) + + label = tkinter.Label(frame, justify=tkinter.LEFT, wraplength=450, + text='Fatal error during ' + 'initialization:\n\n' + msg) + label.grid(padx=5, pady=5) + + button = tkinter.Button(frame, text='Close', + command=lambda: root.quit()) + button.grid(pady=5) + + frame.grid() + frame.mainloop() + + sys.exit(0) + try: - core_mode - except NameError: - pass - else: - return + from PyQt5 import QtGui # noqa (F401) + from PyQt5 import QtWidgets # noqa (F401) + from PyQt5 import uic as QtUic + except ImportError as e: + die('An essential dependency ({0}) could not be imported and is ' + 'probably missing'.format(str(e)[str(e)[:-1].rfind('\'')+1:-1])) + + globals().update({ + 'QtGui': QtGui, + 'QtWidgets': QtWidgets, + 'QtUic': QtUic, + 'die': die + }) + - class _Args: - core_mode = False - kiosk_mode = False +def init(core_mode=False, kiosk_mode=False, window_title='Metro', + local_path='~/.metro', profile_path='~/.metro/profiles'): + import os + import pkg_resources - init(_Args, 'Metro') + src_path = os.path.dirname(os.path.realpath(__file__)) + + local_path = os.path.expanduser(local_path) + os.makedirs(local_path, exist_ok=True) + + profile_path = os.path.expanduser(profile_path) + os.makedirs(profile_path, exist_ok=True) + + globals().update({ + 'WINDOW_TITLE': window_title, + 'SRC_ROOT': src_path, + 'LOCAL_PATH': local_path, + 'PROFILE_PATH': profile_path, + 'resource_exists': pkg_resources.resource_exists, + 'resource_filename': pkg_resources.resource_filename, + 'core_mode': core_mode, + 'kiosk_mode': kiosk_mode + }) + + # Initialize GUI modules if not in core mode + if not core_mode: + init_gui() + + # Initialize the core modules + init_core() def start(prog_name='metro', window_title='Metro', cli_hook=None): args, argv_left = parse_args(prog_name, cli_hook=cli_hook) - init(args, window_title) + init(args.core_mode, args.kiosk_mode, window_title) from .frontend import application - if core_mode: # noqa + if args.core_mode: app_class = application.CoreApplication else: app_class = application.GuiApplication diff --git a/src/metro/devices/abstract/parallel_operator.py b/src/metro/devices/abstract/parallel_operator.py index d558d8e..c12b951 100755 --- a/src/metro/devices/abstract/parallel_operator.py +++ b/src/metro/devices/abstract/parallel_operator.py @@ -25,8 +25,7 @@ import traceback import metro -metro.init_mp_support() - +metro.init() # reinitialization necessary for multiprocessing _targets = {} Target = collections.namedtuple('Target', ['name', 'process', 'active', diff --git a/src/metro/frontend/application.py b/src/metro/frontend/application.py index 06fa711..b4785f3 100644 --- a/src/metro/frontend/application.py +++ b/src/metro/frontend/application.py @@ -57,6 +57,17 @@ def _bootstrap(self, args, version=None, version_short=None): # a PyQt5 application in this case. sys.excepthook = _on_exception + # Set AppUserModelID for Windows 7 and later so that Metro uses + # its assigned taskbar icon instead of grabbing the one with the + # same AppUserModelID (would probably result in no icon at all) + if os.name == 'nt': + try: + myappid = u"{}.{}".format(metro.SRC_ROOT, metro.WINDOW_TITLE) + from ctypes import windll + windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) + except AttributeError: + pass + metro.app = self metro.experimental = args.experimental diff --git a/src/metro/services/devices.py b/src/metro/services/devices.py index 5c18678..8db31a8 100755 --- a/src/metro/services/devices.py +++ b/src/metro/services/devices.py @@ -383,6 +383,8 @@ def _prepare(self, name, parent, args, state, entry_point): if isinstance(self, QtCore.QObject): self.destroyed.connect(_on_device_destroyed) + # for correct/full initialization set as visible first + self.setVisible(True) if 'visible' in state: self.setVisible(state['visible'])