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