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