|
1 #!/usr/bin/env python3 |
|
2 # -*- coding: utf-8 -*- |
|
3 |
|
4 # Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
5 # |
|
6 |
|
7 """ |
|
8 eric Python IDE. |
|
9 |
|
10 This is the main Python script that performs the necessary initialization |
|
11 of the IDE and starts the Qt event loop. |
|
12 """ |
|
13 |
|
14 import contextlib |
|
15 import io |
|
16 import logging |
|
17 import multiprocessing |
|
18 import os |
|
19 import sys |
|
20 import traceback |
|
21 import time |
|
22 |
|
23 originalPathString = os.getenv("PATH") |
|
24 |
|
25 # generate list of arguments to be remembered for a restart |
|
26 restartArgsList = [ |
|
27 "--no-splash", |
|
28 "--plugin", |
|
29 "--debug", |
|
30 "--config", |
|
31 "--settings", |
|
32 "--disable-crash", |
|
33 "--disable-plugin", |
|
34 ] |
|
35 restartArgs = [arg for arg in sys.argv[1:] if arg.split("=", 1)[0] in restartArgsList] |
|
36 |
|
37 sys.path.insert(1, os.path.dirname(__file__)) |
|
38 |
|
39 try: |
|
40 from PyQt6.QtCore import qWarning, QLibraryInfo, QTimer, QCoreApplication |
|
41 except ImportError: |
|
42 try: |
|
43 from tkinter import messagebox |
|
44 except ImportError: |
|
45 sys.exit(100) |
|
46 messagebox.showerror( |
|
47 "eric7 Error", |
|
48 "PyQt could not be imported. Please make sure" |
|
49 " it is installed and accessible.", |
|
50 ) |
|
51 sys.exit(100) |
|
52 |
|
53 try: |
|
54 from PyQt6 import QtWebEngineWidgets # __IGNORE_WARNING__ __IGNORE_EXCEPTION__ |
|
55 from PyQt6.QtWebEngineCore import QWebEngineUrlScheme |
|
56 |
|
57 WEBENGINE_AVAILABLE = True |
|
58 except ImportError: |
|
59 WEBENGINE_AVAILABLE = False |
|
60 |
|
61 # some global variables needed to start the application |
|
62 args = None |
|
63 mainWindow = None |
|
64 splash = None |
|
65 inMainLoop = False |
|
66 app = None |
|
67 |
|
68 if "--debug" in sys.argv: |
|
69 del sys.argv[sys.argv.index("--debug")] |
|
70 logging.basicConfig(level=logging.DEBUG) |
|
71 |
|
72 for arg in sys.argv[:]: |
|
73 if arg.startswith("--config="): |
|
74 import Globals |
|
75 |
|
76 configDir = arg.replace("--config=", "") |
|
77 Globals.setConfigDir(configDir) |
|
78 sys.argv.remove(arg) |
|
79 elif arg.startswith("--settings="): |
|
80 from PyQt6.QtCore import QSettings |
|
81 |
|
82 settingsDir = os.path.expanduser(arg.replace("--settings=", "")) |
|
83 if not os.path.isdir(settingsDir): |
|
84 os.makedirs(settingsDir) |
|
85 QSettings.setPath( |
|
86 QSettings.Format.IniFormat, QSettings.Scope.UserScope, settingsDir |
|
87 ) |
|
88 sys.argv.remove(arg) |
|
89 |
|
90 # make Third-Party package available as a packages repository |
|
91 sys.path.insert(2, os.path.join(os.path.dirname(__file__), "ThirdParty", "Jasy")) |
|
92 sys.path.insert(2, os.path.join(os.path.dirname(__file__), "DebugClients", "Python")) |
|
93 |
|
94 from EricWidgets.EricApplication import EricApplication |
|
95 |
|
96 |
|
97 def handleSingleApplication(ddindex): |
|
98 """ |
|
99 Global function to handle the single application mode. |
|
100 |
|
101 @param ddindex index of a '--' option in the options list |
|
102 """ |
|
103 from EricWidgets.EricSingleApplication import EricSingleApplicationClient |
|
104 |
|
105 client = EricSingleApplicationClient() |
|
106 res = client.connect() |
|
107 if res > 0: |
|
108 if "--no-splash" in sys.argv and sys.argv.index("--no-splash") < ddindex: |
|
109 sys.argv.remove("--no-splash") |
|
110 ddindex -= 1 |
|
111 if "--no-open" in sys.argv and sys.argv.index("--no-open") < ddindex: |
|
112 sys.argv.remove("--no-open") |
|
113 ddindex -= 1 |
|
114 if "--no-crash" in sys.argv and sys.argv.index("--no-crash") < ddindex: |
|
115 sys.argv.remove("--no-crash") |
|
116 if ( |
|
117 "--disable-crash" in sys.argv |
|
118 and sys.argv.index("--disable-crash") < ddindex |
|
119 ): |
|
120 sys.argv.remove("--disable-crash") |
|
121 ddindex -= 1 |
|
122 if "--debug" in sys.argv and sys.argv.index("--debug") < ddindex: |
|
123 sys.argv.remove("--debug") |
|
124 ddindex -= 1 |
|
125 for arg in sys.argv: |
|
126 if arg.startswith("--config=") and sys.argv.index(arg) < ddindex: |
|
127 sys.argv.remove(arg) |
|
128 ddindex -= 1 |
|
129 break |
|
130 for arg in sys.argv: |
|
131 if arg.startswith("--plugin=") and sys.argv.index(arg) < ddindex: |
|
132 sys.argv.remove(arg) |
|
133 ddindex -= 1 |
|
134 break |
|
135 for arg in sys.argv[:]: |
|
136 if arg.startswith("--disable-plugin=") and sys.argv.index(arg) < ddindex: |
|
137 sys.argv.remove(arg) |
|
138 ddindex -= 1 |
|
139 |
|
140 if len(sys.argv) > 1: |
|
141 client.processArgs(sys.argv[1:]) |
|
142 sys.exit(0) |
|
143 elif res < 0: |
|
144 print("eric7: {0}".format(client.errstr())) |
|
145 # __IGNORE_WARNING_M801__ |
|
146 sys.exit(res) |
|
147 |
|
148 |
|
149 def excepthook(excType, excValue, tracebackobj): |
|
150 """ |
|
151 Global function to catch unhandled exceptions. |
|
152 |
|
153 @param excType exception type |
|
154 @param excValue exception value |
|
155 @param tracebackobj traceback object |
|
156 """ |
|
157 from UI.Info import BugAddress |
|
158 import Utilities |
|
159 import Globals |
|
160 |
|
161 # Workaround for a strange issue with QScintilla |
|
162 if str(excValue) == "unable to convert a QVariant back to a Python object": |
|
163 return |
|
164 |
|
165 separator = "-" * 80 |
|
166 logFile = os.path.join(Globals.getConfigDir(), "eric7_error.log") |
|
167 notice = ( |
|
168 """An unhandled exception occurred. Please report the problem\n""" |
|
169 """using the error reporting dialog or via email to <{0}>.\n""" |
|
170 """A log has been written to "{1}".\n\nError information:\n""".format( |
|
171 BugAddress, logFile |
|
172 ) |
|
173 ) |
|
174 timeString = time.strftime("%Y-%m-%d, %H:%M:%S") |
|
175 |
|
176 versionInfo = "\n{0}\n{1}".format(separator, Utilities.generateVersionInfo()) |
|
177 pluginVersionInfo = Utilities.generatePluginsVersionInfo() |
|
178 if pluginVersionInfo: |
|
179 versionInfo += "\n{0}\n{1}".format(separator, pluginVersionInfo) |
|
180 distroInfo = Utilities.generateDistroInfo() |
|
181 if distroInfo: |
|
182 versionInfo += "\n{0}\n{1}".format(separator, distroInfo) |
|
183 |
|
184 if isinstance(excType, str): |
|
185 tbinfo = tracebackobj |
|
186 else: |
|
187 tbinfofile = io.StringIO() |
|
188 traceback.print_tb(tracebackobj, None, tbinfofile) |
|
189 tbinfofile.seek(0) |
|
190 tbinfo = tbinfofile.read() |
|
191 errmsg = "{0}: \n{1}".format(str(excType), str(excValue)) |
|
192 sections = ["", separator, timeString, separator, errmsg, separator, tbinfo] |
|
193 msg = "\n".join(sections) |
|
194 with contextlib.suppress(OSError), open(logFile, "w", encoding="utf-8") as f: |
|
195 f.write(msg) |
|
196 f.write(versionInfo) |
|
197 |
|
198 if inMainLoop: |
|
199 warning = notice + msg + versionInfo |
|
200 # Escape &<> otherwise it's not visible in the error dialog |
|
201 warning = ( |
|
202 warning.replace("&", "&").replace(">", ">").replace("<", "<") |
|
203 ) |
|
204 qWarning(warning) |
|
205 else: |
|
206 warning = notice + msg + versionInfo |
|
207 print(warning) # __IGNORE_WARNING_M801__ |
|
208 |
|
209 |
|
210 def uiStartUp(): |
|
211 """ |
|
212 Global function to finalize the start up of the main UI. |
|
213 |
|
214 Note: It is activated by a zero timeout single-shot timer. |
|
215 """ |
|
216 global args, mainWindow, splash |
|
217 |
|
218 if splash: |
|
219 splash.finish(mainWindow) |
|
220 del splash |
|
221 |
|
222 mainWindow.checkForErrorLog() |
|
223 mainWindow.processArgs(args) |
|
224 mainWindow.processInstallInfoFile() |
|
225 mainWindow.checkProjectsWorkspace() |
|
226 mainWindow.checkConfigurationStatus() |
|
227 mainWindow.performVersionCheck() |
|
228 mainWindow.checkPluginUpdatesAvailable() |
|
229 mainWindow.autoConnectIrc() |
|
230 |
|
231 |
|
232 def main(): |
|
233 """ |
|
234 Main entry point into the application. |
|
235 """ |
|
236 from Globals import AppInfo |
|
237 import Globals |
|
238 |
|
239 global app, args, mainWindow, splash, restartArgs, inMainLoop |
|
240 |
|
241 sys.excepthook = excepthook |
|
242 multiprocessing.set_start_method("spawn") |
|
243 |
|
244 from PyQt6.QtGui import QGuiApplication |
|
245 |
|
246 QGuiApplication.setDesktopFileName("eric7.desktop") |
|
247 |
|
248 options = [ |
|
249 ( |
|
250 "--config=configDir", |
|
251 "use the given directory as the one containing the config files", |
|
252 ), |
|
253 ("--debug", "activate debugging output to the console"), |
|
254 ("--no-splash", "don't show the splash screen"), |
|
255 ("--no-open", "don't open anything at startup except that given in command"), |
|
256 ("--no-crash", "don't check for a crash session file on startup"), |
|
257 ("--disable-crash", "disable the support for crash sessions"), |
|
258 ( |
|
259 "--disable-plugin=<plug-in name>", |
|
260 "disable the given plug-in (may be repeated)", |
|
261 ), |
|
262 ("--plugin=plugin-file", "load the given plugin file (plugin development)"), |
|
263 ( |
|
264 "--settings=settingsDir", |
|
265 "use the given directory to store the settings files", |
|
266 ), |
|
267 ("--small-screen", "adjust the interface for screens smaller than FHD"), |
|
268 ("--start-file", "load the most recently opened file"), |
|
269 ("--start-multi", "load the most recently opened multi-project"), |
|
270 ("--start-project", "load the most recently opened project"), |
|
271 ("--start-session", "load the global session file"), |
|
272 ("--", "indicate that there are options for the program to be debugged"), |
|
273 ("", "(everything after that is considered arguments for this program)"), |
|
274 ] |
|
275 appinfo = AppInfo.makeAppInfo( |
|
276 sys.argv, |
|
277 "Eric7", |
|
278 "[project | files... [--] [debug-options]]", |
|
279 "A Python IDE", |
|
280 options, |
|
281 ) |
|
282 |
|
283 if "__PYVENV_LAUNCHER__" in os.environ: |
|
284 del os.environ["__PYVENV_LAUNCHER__"] |
|
285 |
|
286 # make sure our executable directory (i.e. that of the used Python |
|
287 # interpreter) is included in the executable search path |
|
288 pathList = os.environ["PATH"].split(os.pathsep) |
|
289 exeDir = os.path.dirname(sys.executable) |
|
290 if exeDir not in pathList: |
|
291 pathList.insert(0, exeDir) |
|
292 os.environ["PATH"] = os.pathsep.join(pathList) |
|
293 |
|
294 from Toolbox import Startup |
|
295 |
|
296 # set the library paths for plugins |
|
297 Startup.setLibraryPaths() |
|
298 |
|
299 if WEBENGINE_AVAILABLE: |
|
300 scheme = QWebEngineUrlScheme(b"qthelp") |
|
301 scheme.setSyntax(QWebEngineUrlScheme.Syntax.Path) |
|
302 scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme) |
|
303 QWebEngineUrlScheme.registerScheme(scheme) |
|
304 |
|
305 app = EricApplication(sys.argv) |
|
306 ddindex = Startup.handleArgs(sys.argv, appinfo) |
|
307 |
|
308 logging.debug("Importing Preferences") |
|
309 import Preferences |
|
310 |
|
311 if Preferences.getUI("SingleApplicationMode"): |
|
312 handleSingleApplication(ddindex) |
|
313 |
|
314 # set the application style sheet |
|
315 app.setStyleSheetFile(Preferences.getUI("StyleSheet")) |
|
316 |
|
317 # set the search path for icons |
|
318 Startup.initializeResourceSearchPath(app) |
|
319 |
|
320 # generate and show a splash window, if not suppressed |
|
321 from UI.SplashScreen import SplashScreen, NoneSplashScreen |
|
322 |
|
323 if "--no-splash" in sys.argv and sys.argv.index("--no-splash") < ddindex: |
|
324 sys.argv.remove("--no-splash") |
|
325 ddindex -= 1 |
|
326 splash = NoneSplashScreen() |
|
327 elif not Preferences.getUI("ShowSplash"): |
|
328 splash = NoneSplashScreen() |
|
329 else: |
|
330 splash = SplashScreen() |
|
331 QCoreApplication.processEvents() |
|
332 |
|
333 # modify the executable search path for the PyQt5 installer |
|
334 if Globals.isWindowsPlatform(): |
|
335 pyqtDataDir = Globals.getPyQt6ModulesDirectory() |
|
336 if os.path.exists(os.path.join(pyqtDataDir, "bin")): |
|
337 path = os.path.join(pyqtDataDir, "bin") |
|
338 else: |
|
339 path = pyqtDataDir |
|
340 os.environ["PATH"] = path + os.pathsep + os.environ["PATH"] |
|
341 |
|
342 pluginFile = None |
|
343 noopen = False |
|
344 nocrash = False |
|
345 disablecrash = False |
|
346 disabledPlugins = [] |
|
347 if "--no-open" in sys.argv and sys.argv.index("--no-open") < ddindex: |
|
348 sys.argv.remove("--no-open") |
|
349 ddindex -= 1 |
|
350 noopen = True |
|
351 if "--no-crash" in sys.argv and sys.argv.index("--no-crash") < ddindex: |
|
352 sys.argv.remove("--no-crash") |
|
353 ddindex -= 1 |
|
354 nocrash = True |
|
355 if "--disable-crash" in sys.argv and sys.argv.index("--disable-crash") < ddindex: |
|
356 sys.argv.remove("--disable-crash") |
|
357 ddindex -= 1 |
|
358 disablecrash = True |
|
359 for arg in sys.argv[:]: |
|
360 if arg.startswith("--disable-plugin=") and sys.argv.index(arg) < ddindex: |
|
361 # extract the plug-in name |
|
362 pluginName = arg.replace("--disable-plugin=", "") |
|
363 sys.argv.remove(arg) |
|
364 ddindex -= 1 |
|
365 disabledPlugins.append(pluginName) |
|
366 for arg in sys.argv: |
|
367 if arg.startswith("--plugin=") and sys.argv.index(arg) < ddindex: |
|
368 # extract the plugin development option |
|
369 pluginFile = arg.replace("--plugin=", "").replace('"', "") |
|
370 sys.argv.remove(arg) |
|
371 ddindex -= 1 |
|
372 pluginFile = os.path.expanduser(pluginFile) |
|
373 pluginFile = os.path.abspath(pluginFile) |
|
374 break |
|
375 |
|
376 # is there a set of filenames or options on the command line, |
|
377 # if so, pass them to the UI |
|
378 if len(sys.argv) > 1: |
|
379 args = sys.argv[1:] |
|
380 |
|
381 # get the Qt translations directory |
|
382 qtTransDir = Preferences.getQtTranslationsDir() |
|
383 if not qtTransDir: |
|
384 qtTransDir = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath) |
|
385 |
|
386 # Load translation files and install them |
|
387 loc = Startup.loadTranslators(qtTransDir, app, ("qscintilla",)) |
|
388 |
|
389 # Initialize SSL stuff |
|
390 from EricNetwork.EricSslUtilities import initSSL |
|
391 |
|
392 initSSL() |
|
393 |
|
394 splash.showMessage(QCoreApplication.translate("eric7_ide", "Starting...")) |
|
395 # We can only import these after creating the EricApplication because they |
|
396 # make Qt calls that need the EricApplication to exist. |
|
397 from UI.UserInterface import UserInterface |
|
398 |
|
399 splash.showMessage( |
|
400 QCoreApplication.translate("eric7_ide", "Generating Main Window...") |
|
401 ) |
|
402 mainWindow = UserInterface( |
|
403 app, |
|
404 loc, |
|
405 splash, |
|
406 pluginFile, |
|
407 disabledPlugins, |
|
408 noopen, |
|
409 nocrash, |
|
410 disablecrash, |
|
411 restartArgs, |
|
412 originalPathString, |
|
413 ) |
|
414 app.lastWindowClosed.connect(app.quit) |
|
415 mainWindow.show() |
|
416 |
|
417 QTimer.singleShot(0, uiStartUp) |
|
418 |
|
419 # generate a graphical error handler |
|
420 from EricWidgets import EricErrorMessage |
|
421 |
|
422 eMsg = EricErrorMessage.qtHandler() |
|
423 eMsg.setMinimumSize(600, 400) |
|
424 |
|
425 # start the event loop |
|
426 inMainLoop = True |
|
427 res = app.exec() |
|
428 logging.debug("Shutting down, result %d", res) |
|
429 logging.shutdown() |
|
430 sys.exit(res) |
|
431 |
|
432 |
|
433 if __name__ == "__main__": |
|
434 main() |