1 /* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
3 SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
4 SPDX-FileCopyrightText: 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7 */
8
9 #include "config.h"
10
11 #include "kwrite.h"
12 #include "kwriteapplication.h"
13
14 #include <ktexteditor/document.h>
15 #include <ktexteditor/editor.h>
16 #include <ktexteditor/view.h>
17
18 #include <KAboutData>
19 #include <KCrash>
20 #include <KDBusService>
21 #include <KLocalizedString>
22 #include <KMessageBox>
23
24 #include <QApplication>
25 #include <QCommandLineParser>
26 #include <QDir>
27 #include <QFileInfo>
28 #include <QTextCodec>
29 #include <QUrlQuery>
30
31 #include <urlinfo.h>
32
33 #ifndef Q_OS_WIN
34 #include <unistd.h>
35 #endif
36 #include <iostream>
37
main(int argc,char ** argv)38 extern "C" Q_DECL_EXPORT int main(int argc, char **argv)
39 {
40 #if !defined(Q_OS_WIN) && !defined(Q_OS_HAIKU)
41 // Prohibit using sudo or kdesu (but allow using the root user directly)
42 if (getuid() == 0) {
43 if (!qEnvironmentVariableIsEmpty("SUDO_USER")) {
44 std::cout << "Executing KWrite with sudo is not possible due to unfixable security vulnerabilities. "
45 "It is also not necessary; simply use KWrite normally, and you will be prompted for "
46 "elevated privileges when saving documents if needed."
47 << std::endl;
48 return EXIT_FAILURE;
49 } else if (!qEnvironmentVariableIsEmpty("KDESU_USER")) {
50 std::cout << "Executing KWrite with kdesu is not possible due to unfixable security vulnerabilities. "
51 "It is also not necessary; simply use KWrite normally, and you will be prompted for "
52 "elevated privileges when saving documents if needed."
53 << std::endl;
54 return EXIT_FAILURE;
55 }
56 }
57 #endif
58
59 /**
60 * enable high dpi support
61 */
62 QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
63 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
64
65 /**
66 * allow fractional scaling
67 * we only activate this on Windows, it seems to creates problems on unices
68 * (and there the fractional scaling with the QT_... env vars as set by KScreen works)
69 * see bug 416078
70 *
71 * we switched to Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor because of font rendering issues
72 * we follow what Krita does here, see https://invent.kde.org/graphics/krita/-/blob/master/krita/main.cc
73 * we raise the Qt requirement to 5.15 as it seems some patches went in after 5.14 that are needed
74 * see Krita comments, too
75 */
76 #if defined(Q_OS_WIN)
77 QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
78 #endif
79
80 /**
81 * Create application first
82 * Enforce application name even if the executable is renamed
83 */
84 QApplication app(argc, argv);
85 app.setApplicationName(QStringLiteral("kwrite"));
86
87 /**
88 * For Windows and macOS: use Breeze if available
89 * Of all tested styles that works the best for us
90 */
91 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
92 QApplication::setStyle(QStringLiteral("breeze"));
93 #endif
94
95 /**
96 * Enable crash handling through KCrash.
97 */
98 KCrash::initialize();
99
100 /**
101 * Connect application with translation catalogs
102 */
103 KLocalizedString::setApplicationDomain("kwrite");
104
105 /**
106 * then use i18n and co
107 */
108 KAboutData aboutData(QStringLiteral("kwrite"),
109 i18n("KWrite"),
110 QStringLiteral(KWRITE_VERSION),
111 i18n("KWrite - Text Editor"),
112 KAboutLicense::LGPL_V2,
113 i18n("(c) 2000-2021 The Kate Authors"),
114 QString(),
115 QStringLiteral("https://kate-editor.org"));
116
117 /**
118 * right dbus prefix == org.kde.
119 */
120 aboutData.setOrganizationDomain(QByteArray("kde.org"));
121
122 /**
123 * desktop file association to make application icon work (e.g. in Wayland window decoration)
124 */
125 aboutData.setDesktopFileName(QStringLiteral("org.kde.kwrite"));
126
127 /**
128 * authors & co.
129 */
130 aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("https://cullmann.io"));
131 aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org"));
132 aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("https://www.alweb.dk"));
133 aboutData.addAuthor(i18n("Joseph Wenninger"),
134 i18n("Core Developer"),
135 QStringLiteral("jowenn@kde.org"),
136 QStringLiteral("http://stud3.tuwien.ac.at/~e9925371"));
137 aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org"));
138 aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org"));
139 aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org"));
140 aboutData.addAuthor(i18n("Matt Newell"),
141 i18nc("Credit text for someone that did testing and some other similar things", "Testing, ..."),
142 QStringLiteral("newellm@proaxis.com"));
143 aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at"));
144 aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz"));
145 aboutData.addAuthor(i18n("Jochen Wilhemly"), i18n("KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de"));
146 aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org"));
147 aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org"));
148 aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org"));
149 aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com"));
150 aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net"));
151 aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org"));
152 aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"),
153 i18n("QA and Scripting"),
154 QStringLiteral("oss@senarclens.eu"),
155 QStringLiteral("http://find-santa.eu/"));
156
157 aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it"));
158 aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu"));
159 aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL"));
160 aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite"));
161 aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG"));
162 aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX"));
163 aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python"));
164 aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python"));
165 aboutData.addCredit(i18n("Daniel Naber"));
166 aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme"));
167 aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list"));
168 aboutData.addCredit(i18n("Carsten Pfeiffer"), i18nc("Credit text for someone that helped a lot", "Very nice help"));
169 aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention"));
170
171 /**
172 * bugzilla
173 */
174 aboutData.setProductName(QByteArray("kate/kwrite"));
175
176 /**
177 * set and register app about data
178 */
179 KAboutData::setApplicationData(aboutData);
180
181 /**
182 * set the program icon
183 */
184 QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"), app.windowIcon()));
185
186 /**
187 * Create command line parser and feed it with known options
188 */
189 QCommandLineParser parser;
190 aboutData.setupCommandLine(&parser);
191
192 // -e/--encoding option
193 const QCommandLineOption useEncoding(QStringList() << QStringLiteral("e") << QStringLiteral("encoding"),
194 i18n("Set encoding for the file to open."),
195 i18n("encoding"));
196 parser.addOption(useEncoding);
197
198 // -l/--line option
199 const QCommandLineOption gotoLine(QStringList() << QStringLiteral("l") << QStringLiteral("line"), i18n("Navigate to this line."), i18n("line"));
200 parser.addOption(gotoLine);
201
202 // -c/--column option
203 const QCommandLineOption gotoColumn(QStringList() << QStringLiteral("c") << QStringLiteral("column"), i18n("Navigate to this column."), i18n("column"));
204 parser.addOption(gotoColumn);
205
206 // -i/--stdin option
207 const QCommandLineOption readStdIn(QStringList() << QStringLiteral("i") << QStringLiteral("stdin"), i18n("Read the contents of stdin."));
208 parser.addOption(readStdIn);
209
210 // --tempfile option
211 const QCommandLineOption tempfile(QStringList() << QStringLiteral("tempfile"), i18n("The files/URLs opened by the application will be deleted after use"));
212 parser.addOption(tempfile);
213
214 // urls to open
215 parser.addPositionalArgument(QStringLiteral("urls"), i18n("Documents to open."), i18n("[urls...]"));
216
217 /**
218 * do the command line parsing
219 */
220 parser.process(app);
221
222 /**
223 * handle standard options
224 */
225 aboutData.processCommandLine(&parser);
226
227 KWriteApplication kapp;
228
229 if (app.isSessionRestored()) {
230 kapp.restore();
231 } else {
232 bool nav = false;
233 int line = 0, column = 0;
234
235 QTextCodec *codec =
236 parser.isSet(QStringLiteral("encoding")) ? QTextCodec::codecForName(parser.value(QStringLiteral("encoding")).toLocal8Bit()) : nullptr;
237
238 if (parser.isSet(QStringLiteral("line"))) {
239 line = parser.value(QStringLiteral("line")).toInt() - 1;
240 nav = true;
241 }
242
243 if (parser.isSet(QStringLiteral("column"))) {
244 column = parser.value(QStringLiteral("column")).toInt() - 1;
245 nav = true;
246 }
247
248 if (parser.positionalArguments().count() == 0) {
249 KWrite *t = kapp.newWindow();
250
251 if (parser.isSet(QStringLiteral("stdin"))) {
252 QTextStream input(stdin, QIODevice::ReadOnly);
253
254 // set chosen codec
255 if (codec) {
256 input.setCodec(codec);
257 }
258
259 QString line;
260 QString text;
261
262 do {
263 line = input.readLine();
264 text.append(line + QLatin1Char('\n'));
265 } while (!line.isNull());
266
267 KTextEditor::Document *doc = t->activeView()->document();
268 if (doc) {
269 // remember codec in document, e.g. to show the right one
270 if (codec) {
271 doc->setEncoding(QString::fromLatin1(codec->name()));
272 }
273 doc->setText(text);
274 }
275 }
276
277 if (nav && t->activeView()) {
278 t->activeView()->setCursorPosition(KTextEditor::Cursor(line, column));
279 }
280 } else {
281 int docs_opened = 0;
282 const auto positionalArguments = parser.positionalArguments();
283 for (const QString &positionalArgument : positionalArguments) {
284 UrlInfo info(positionalArgument);
285 if (nav) {
286 info.cursor = KTextEditor::Cursor(line, column);
287 }
288
289 // this file is no local dir, open it, else warn
290 bool noDir = !info.url.isLocalFile() || !QFileInfo(info.url.toLocalFile()).isDir();
291
292 if (noDir) {
293 ++docs_opened;
294 KWrite *t = kapp.newWindow();
295
296 if (codec) {
297 t->activeView()->document()->setEncoding(QString::fromLatin1(codec->name()));
298 }
299
300 t->loadURL(info.url);
301
302 if (info.cursor.isValid()) {
303 t->activeView()->setCursorPosition(info.cursor);
304 } else if (info.url.hasQuery()) {
305 QUrlQuery q(info.url);
306 QString lineStr = q.queryItemValue(QStringLiteral("line"));
307 QString columnStr = q.queryItemValue(QStringLiteral("column"));
308
309 line = lineStr.toInt();
310 if (line > 0) {
311 line--;
312 }
313
314 column = columnStr.toInt();
315 if (column > 0) {
316 column--;
317 }
318
319 t->activeView()->setCursorPosition(KTextEditor::Cursor(line, column));
320 }
321 } else {
322 KMessageBox::sorry(nullptr, i18n("The file '%1' could not be opened: it is not a normal file, it is a folder.", info.url.toString()));
323 }
324 }
325 if (!docs_opened) {
326 ::exit(1); // see https://bugs.kde.org/show_bug.cgi?id=124708
327 }
328 }
329 }
330
331 // no window there, uh, ohh, for example borked session config !!!
332 // create at least one !!
333 if (kapp.noWindows()) {
334 kapp.newWindow();
335 }
336
337 /**
338 * finally register this kwrite instance for dbus, don't die if no dbus is around!
339 */
340 const KDBusService dbusService(KDBusService::Multiple | KDBusService::NoExitOnFailure);
341
342 /**
343 * Run the event loop
344 */
345 return app.exec();
346 }
347