1#!/usr/bin/env python3
2# vim: ts=8:sts=8:sw=8:noexpandtab
3
4# ReText
5# Copyright 2011-2021 Dmitry Shachnev
6#
7# This program 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 2 of the License, or
10# (at your option) any later version.
11#
12# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
19
20import ctypes
21import multiprocessing
22import sys
23import signal
24import markups
25from os import devnull
26from os.path import join
27from ReText import datadirs, settings, globalSettings, app_version
28from ReText import initializeDataDirs
29from ReText.window import ReTextWindow
30
31from PyQt5.QtCore import QCommandLineOption, QCommandLineParser, QFile, \
32 QFileInfo, QIODevice, QLibraryInfo, QTextStream, QTranslator, Qt
33from PyQt5.QtWidgets import QApplication
34from PyQt5.QtNetwork import QNetworkProxyFactory
35from PyQt5.QtDBus import QDBusConnection, QDBusInterface
36
37def canonicalize(option):
38	if option == '-':
39		return option
40	return QFileInfo(option).canonicalFilePath()
41
42def main():
43	multiprocessing.set_start_method('spawn')
44
45	if markups.__version_tuple__ < (2, ):
46		sys.exit('Error: ReText needs PyMarkups 2.0 or newer to run.')
47
48	# If we're running on Windows without a console, then discard stdout
49	# and save stderr to a file to facilitate debugging in case of crashes.
50	if sys.executable.endswith('pythonw.exe'):
51		sys.stdout = open(devnull, 'w')
52		sys.stderr = open('stderr.log', 'w')
53
54	try:
55		# See https://github.com/retext-project/retext/issues/399
56		# and https://launchpad.net/bugs/941826
57		ctypes.CDLL('libGL.so.1', ctypes.RTLD_GLOBAL)
58	except OSError:
59		pass
60
61	# Needed for Qt WebEngine on Windows
62	QApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts)
63	QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps)
64	app = QApplication(sys.argv)
65	app.setOrganizationName("ReText project")
66	app.setApplicationName("ReText")
67	app.setApplicationDisplayName("ReText")
68	app.setApplicationVersion(app_version)
69	app.setOrganizationDomain('mitya57.me')
70	app.setDesktopFileName('me.mitya57.ReText.desktop')
71	QNetworkProxyFactory.setUseSystemConfiguration(True)
72
73	initializeDataDirs()
74	RtTranslator = QTranslator()
75	for path in datadirs:
76		if RtTranslator.load('retext_' + globalSettings.uiLanguage,
77		                     join(path, 'locale')):
78			break
79	QtTranslator = QTranslator()
80	QtTranslator.load("qtbase_" + globalSettings.uiLanguage,
81		QLibraryInfo.location(QLibraryInfo.LibraryLocation.TranslationsPath))
82	app.installTranslator(RtTranslator)
83	app.installTranslator(QtTranslator)
84
85	parser = QCommandLineParser()
86	parser.addHelpOption()
87	parser.addVersionOption()
88	previewOption = QCommandLineOption('preview',
89		QApplication.translate('main', 'Open the files in preview mode'))
90	newWindowOption = QCommandLineOption('new-window',
91		QApplication.translate('main', 'Create a new window even if there is an existing one'))
92	parser.addOption(previewOption)
93	parser.addOption(newWindowOption)
94	parser.addPositionalArgument('files',
95		QApplication.translate('main', 'List of files to open'),
96		'[files...]')
97
98	parser.process(app)
99	filesToOpen = parser.positionalArguments()
100
101	print('Using configuration file:', settings.fileName())
102	if globalSettings.appStyleSheet:
103		sheetfile = QFile(globalSettings.appStyleSheet)
104		sheetfile.open(QIODevice.OpenModeFlag.ReadOnly)
105		app.setStyleSheet(QTextStream(sheetfile).readAll())
106		sheetfile.close()
107	window = ReTextWindow()
108
109	openInExistingWindow = (globalSettings.openFilesInExistingWindow
110		and not parser.isSet(newWindowOption))
111	connection = QDBusConnection.sessionBus()
112	if connection.isConnected() and openInExistingWindow:
113		connection.registerObject('/', window, QDBusConnection.RegisterOption.ExportAllSlots)
114		serviceName = 'me.mitya57.ReText'
115		if not connection.registerService(serviceName) and filesToOpen:
116			print('Opening the file(s) in the existing window of ReText.')
117			iface = QDBusInterface(serviceName, '/', '', connection)
118			for fileName in filesToOpen:
119				iface.call('openFileWrapper', fileName)
120			qWidgetIface = QDBusInterface(serviceName, '/', 'org.qtproject.Qt.QWidget', connection)
121			qWidgetIface.call('raise')
122			sys.exit(0)
123
124	window.show()
125	# ReText can change directory when loading files, so we
126	# need to have a list of canonical names before loading
127	fileNames = list(map(canonicalize, filesToOpen))
128	readStdIn = False
129
130	if globalSettings.openLastFilesOnStartup:
131		window.restoreLastOpenedFiles()
132	for fileName in fileNames:
133		if QFile.exists(fileName):
134			window.openFileWrapper(fileName)
135			if parser.isSet(previewOption):
136				window.actionPreview.setChecked(True)
137				window.preview(True)
138		elif fileName == '-':
139			readStdIn = True
140
141	inputData = ''
142	if readStdIn and sys.stdin is not None:
143		if sys.stdin.isatty():
144			print('Reading stdin, press ^D to end...')
145		inputData = sys.stdin.read()
146	if inputData or not window.tabWidget.count():
147		window.createNew(inputData)
148	signal.signal(signal.SIGINT, lambda sig, frame: window.close())
149	sys.exit(app.exec())
150
151if __name__ == '__main__':
152	main()
153