1 /*
2 Copyright © 2014-2019 by The qTox Project Contributors
3
4 This file is part of qTox, a Qt-based graphical interface for Tox.
5
6 qTox is libre software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 qTox is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "src/audio/audio.h"
21 #include "src/ipc.h"
22 #include "src/net/toxuri.h"
23 #include "src/nexus.h"
24 #include "src/persistence/profile.h"
25 #include "src/persistence/settings.h"
26 #include "src/persistence/toxsave.h"
27 #include "src/video/camerasource.h"
28 #include "src/widget/loginscreen.h"
29 #include "src/widget/translator.h"
30 #include "widget/widget.h"
31 #include <QApplication>
32 #include <QCommandLineParser>
33 #include <QDateTime>
34 #include <QDebug>
35 #include <QDir>
36 #include <QFile>
37 #include <QFontDatabase>
38 #include <QMutex>
39 #include <QMutexLocker>
40
41 #include <QtWidgets/QMessageBox>
42 #include <ctime>
43 #include <sodium.h>
44 #include <stdio.h>
45
46 #if defined(Q_OS_OSX)
47 #include "platform/install_osx.h"
48 #endif
49
50 #if defined(Q_OS_UNIX)
51 #include "platform/posixsignalnotifier.h"
52 #endif
53
54 #ifdef LOG_TO_FILE
55 static QAtomicPointer<FILE> logFileFile = nullptr;
56 static QList<QByteArray>* logBuffer =
57 new QList<QByteArray>(); // Store log messages until log file opened
58 QMutex* logBufferMutex = new QMutex();
59 #endif
60
cleanup()61 void cleanup()
62 {
63 // force save early even though destruction saves, because Windows OS will
64 // close qTox before cleanup() is finished if logging out or shutting down,
65 // once the top level window has exited, which occurs in ~Widget within
66 // ~Nexus. Re-ordering Nexus destruction is not trivial.
67 auto& s = Settings::getInstance();
68 s.saveGlobal();
69 s.savePersonal();
70 s.sync();
71
72 Nexus::destroyInstance();
73 CameraSource::destroyInstance();
74 Settings::destroyInstance();
75 qDebug() << "Cleanup success";
76
77 #ifdef LOG_TO_FILE
78 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
79 FILE* f = logFileFile.loadRelaxed();
80 #else
81 FILE* f = logFileFile.load();
82 #endif
83 if (f != nullptr) {
84 fclose(f);
85 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
86 logFileFile.storeRelaxed(nullptr); // atomically disable logging to file
87 #else
88 logFileFile.store(nullptr); // atomically disable logging to file
89 #endif
90 }
91 #endif
92 }
93
logMessageHandler(QtMsgType type,const QMessageLogContext & ctxt,const QString & msg)94 void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QString& msg)
95 {
96 // Silence qWarning spam due to bug in QTextBrowser (trying to open a file for base64 images)
97 if (ctxt.function == QString("virtual bool QFSFileEngine::open(QIODevice::OpenMode)")
98 && msg == QString("QFSFileEngine::open: No file name specified"))
99 return;
100
101 QString file = ctxt.file;
102 // We're not using QT_MESSAGELOG_FILE here, because that can be 0, NULL, or
103 // nullptr in release builds.
104 QString path = QString(__FILE__);
105 path = path.left(path.lastIndexOf('/') + 1);
106 if (file.startsWith(path)) {
107 file = file.mid(path.length());
108 }
109
110 // Time should be in UTC to save user privacy on log sharing
111 QTime time = QDateTime::currentDateTime().toUTC().time();
112 QString LogMsg =
113 QString("[%1 UTC] %2:%3 : ").arg(time.toString("HH:mm:ss.zzz")).arg(file).arg(ctxt.line);
114 switch (type) {
115 case QtDebugMsg:
116 LogMsg += "Debug";
117 break;
118 case QtInfoMsg:
119 LogMsg += "Info";
120 break;
121 case QtWarningMsg:
122 LogMsg += "Warning";
123 break;
124 case QtCriticalMsg:
125 LogMsg += "Critical";
126 break;
127 case QtFatalMsg:
128 LogMsg += "Fatal";
129 break;
130 default:
131 break;
132 }
133
134 LogMsg += ": " + msg + "\n";
135 QByteArray LogMsgBytes = LogMsg.toUtf8();
136 fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), stderr);
137
138 #ifdef LOG_TO_FILE
139 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
140 FILE* logFilePtr = logFileFile.loadRelaxed(); // atomically load the file pointer
141 #else
142 FILE* logFilePtr = logFileFile.load(); // atomically load the file pointer
143 #endif
144 if (!logFilePtr) {
145 logBufferMutex->lock();
146 if (logBuffer)
147 logBuffer->append(LogMsgBytes);
148
149 logBufferMutex->unlock();
150 } else {
151 logBufferMutex->lock();
152 if (logBuffer) {
153 // empty logBuffer to file
154 foreach (QByteArray msg, *logBuffer)
155 fwrite(msg.constData(), 1, msg.size(), logFilePtr);
156
157 delete logBuffer; // no longer needed
158 logBuffer = nullptr;
159 }
160 logBufferMutex->unlock();
161
162 fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), logFilePtr);
163 fflush(logFilePtr);
164 }
165 #endif
166 }
167
main(int argc,char * argv[])168 int main(int argc, char* argv[])
169 {
170 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
171 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
172 QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
173 #endif
174
175 qInstallMessageHandler(logMessageHandler);
176
177 // initialize random number generator
178 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
179 qsrand(time(nullptr));
180 #endif
181
182 std::unique_ptr<QApplication> a(new QApplication(argc, argv));
183
184 #if defined(Q_OS_UNIX)
185 // PosixSignalNotifier is used only for terminating signals,
186 // so it's connected directly to quit() without any filtering.
187 QObject::connect(&PosixSignalNotifier::globalInstance(), &PosixSignalNotifier::activated,
188 a.get(), &QApplication::quit);
189 PosixSignalNotifier::watchCommonTerminatingSignals();
190 #endif
191
192 a->setApplicationName("qTox");
193 #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
194 a->setDesktopFileName("io.github.qtox.qTox");
195 #endif
196 a->setApplicationVersion("\nGit commit: " + QString(GIT_VERSION));
197
198 // Install Unicode 6.1 supporting font
199 // Keep this as close to the beginning of `main()` as possible, otherwise
200 // on systems that have poor support for Unicode qTox will look bad.
201 if (QFontDatabase::addApplicationFont("://font/DejaVuSans.ttf") == -1) {
202 qWarning() << "Couldn't load font";
203 }
204
205
206 #if defined(Q_OS_OSX)
207 // TODO: Add setting to enable this feature.
208 // osx::moveToAppFolder();
209 osx::migrateProfiles();
210 #endif
211
212 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
213 qsrand(time(nullptr));
214 #endif
215 Settings& settings = Settings::getInstance();
216 QString locale = settings.getTranslation();
217 Translator::translate(locale);
218
219 // Process arguments
220 QCommandLineParser parser;
221 parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION));
222 parser.addHelpOption();
223 parser.addVersionOption();
224 parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse"));
225 parser.addOption(
226 QCommandLineOption(QStringList() << "p"
227 << "profile",
228 QObject::tr("Starts new instance and loads specified profile."),
229 QObject::tr("profile")));
230 parser.addOption(
231 QCommandLineOption(QStringList() << "l"
232 << "login",
233 QObject::tr("Starts new instance and opens the login screen.")));
234 parser.addOption(QCommandLineOption(QStringList() << "I"
235 << "IPv6",
236 QObject::tr("Sets IPv6 <on>/<off>. Default is ON."),
237 QObject::tr("on/off")));
238 parser.addOption(QCommandLineOption(QStringList() << "U"
239 << "UDP",
240 QObject::tr("Sets UDP <on>/<off>. Default is ON."),
241 QObject::tr("on/off")));
242 parser.addOption(
243 QCommandLineOption(QStringList() << "L"
244 << "LAN",
245 QObject::tr(
246 "Sets LAN discovery <on>/<off>. UDP off overrides. Default is ON."),
247 QObject::tr("on/off")));
248 parser.addOption(QCommandLineOption(QStringList() << "P"
249 << "proxy",
250 QObject::tr("Sets proxy settings. Default is NONE."),
251 QObject::tr("(SOCKS5/HTTP/NONE):(ADDRESS):(PORT)")));
252 parser.process(*a);
253
254 uint32_t profileId = settings.getCurrentProfileId();
255 IPC ipc(profileId);
256 if (ipc.isAttached()) {
257 QObject::connect(&settings, &Settings::currentProfileIdChanged, &ipc, &IPC::setProfileId);
258 } else {
259 qWarning() << "Can't init IPC, maybe we're in a jail? Continuing with reduced multi-client functionality.";
260 }
261
262 // For the auto-updater
263 if (sodium_init() < 0) {
264 qCritical() << "Can't init libsodium";
265 return EXIT_FAILURE;
266 }
267
268 #ifdef LOG_TO_FILE
269 QString logFileDir = settings.getAppCacheDirPath();
270 QDir(logFileDir).mkpath(".");
271
272 QString logfile = logFileDir + "qtox.log";
273 FILE* mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a");
274
275 // Trim log file if over 1MB
276 if (QFileInfo(logfile).size() > 1000000) {
277 qDebug() << "Log file over 1MB, rotating...";
278
279 // close old logfile (need for windows)
280 if (mainLogFilePtr)
281 fclose(mainLogFilePtr);
282
283 QDir dir(logFileDir);
284
285 // Check if log.1 already exists, and if so, delete it
286 if (dir.remove(logFileDir + "qtox.log.1"))
287 qDebug() << "Removed old log successfully";
288 else
289 qWarning() << "Unable to remove old log file";
290
291 if (!dir.rename(logFileDir + "qtox.log", logFileDir + "qtox.log.1"))
292 qCritical() << "Unable to move logs";
293
294 // open a new logfile
295 mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a");
296 }
297
298 if (!mainLogFilePtr)
299 qCritical() << "Couldn't open logfile" << logfile;
300
301 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
302 logFileFile.storeRelaxed(mainLogFilePtr); // atomically set the logFile
303 #else
304 logFileFile.store(mainLogFilePtr); // atomically set the logFile
305 #endif
306 #endif
307
308 // Windows platform plugins DLL hell fix
309 QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
310 a->addLibraryPath("platforms");
311
312 qDebug() << "commit: " << GIT_VERSION;
313
314 QString profileName;
315 bool autoLogin = settings.getAutoLogin();
316
317 uint32_t ipcDest = 0;
318 bool doIpc = ipc.isAttached();
319 QString eventType, firstParam;
320 if (parser.isSet("p")) {
321 profileName = parser.value("p");
322 if (!Profile::exists(profileName)) {
323 qWarning() << "-p profile" << profileName + ".tox"
324 << "doesn't exist, opening login screen";
325 doIpc = false;
326 autoLogin = false;
327 } else {
328 ipcDest = Settings::makeProfileId(profileName);
329 autoLogin = true;
330 }
331 } else if (parser.isSet("l")) {
332 doIpc = false;
333 autoLogin = false;
334 } else {
335 profileName = settings.getCurrentProfile();
336 }
337
338 if (parser.positionalArguments().empty()) {
339 eventType = "activate";
340 } else {
341 firstParam = parser.positionalArguments()[0];
342 // Tox URIs. If there's already another qTox instance running, we ask it to handle the URI
343 // and we exit
344 // Otherwise we start a new qTox instance and process it ourselves
345 if (firstParam.startsWith("tox:")) {
346 eventType = "uri";
347 } else if (firstParam.endsWith(".tox")) {
348 eventType = "save";
349 } else {
350 qCritical() << "Invalid argument";
351 return EXIT_FAILURE;
352 }
353 }
354
355 if (doIpc && !ipc.isCurrentOwner()) {
356 time_t event = ipc.postEvent(eventType, firstParam.toUtf8(), ipcDest);
357 // If someone else processed it, we're done here, no need to actually start qTox
358 if (ipc.waitUntilAccepted(event, 2)) {
359 if (eventType == "activate") {
360 qDebug()
361 << "Another qTox instance is already running. If you want to start a second "
362 "instance, please open login screen (qtox -l) or start with a profile (qtox "
363 "-p <profile name>).";
364 } else {
365 qDebug() << "Event" << eventType << "was handled by other client.";
366 }
367 return EXIT_SUCCESS;
368 }
369 }
370
371 if (!Settings::verifyProxySettings(parser)) {
372 return -1;
373 }
374
375 // TODO(sudden6): remove once we get rid of Nexus
376 Nexus& nexus = Nexus::getInstance();
377 // TODO(kriby): Consider moving application initializing variables into a globalSettings object
378 // note: Because Settings is shouldering global settings as well as model specific ones it
379 // cannot be integrated into a central model object yet
380 nexus.setSettings(&settings);
381
382 // Autologin
383 // TODO (kriby): Shift responsibility of linking views to model objects from nexus
384 // Further: generate view instances separately (loginScreen, mainGUI, audio)
385 Profile* profile = nullptr;
386 if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) {
387 profile = Profile::loadProfile(profileName, &parser);
388 if (!profile) {
389 QMessageBox::information(nullptr, QObject::tr("Error"),
390 QObject::tr("Failed to load profile automatically."));
391 }
392 }
393 if (profile) {
394 nexus.bootstrapWithProfile(profile);
395 } else {
396 nexus.setParser(&parser);
397 int returnval = nexus.showLogin(profileName);
398 if (returnval == QDialog::Rejected) {
399 return -1;
400 }
401 }
402
403 if (ipc.isAttached()) {
404 // Start to accept Inter-process communication
405 ipc.registerEventHandler("uri", &toxURIEventHandler);
406 ipc.registerEventHandler("save", &toxSaveEventHandler);
407 ipc.registerEventHandler("activate", &toxActivateEventHandler);
408 }
409
410 // Event was not handled by already running instance therefore we handle it ourselves
411 if (eventType == "uri")
412 handleToxURI(firstParam.toUtf8());
413 else if (eventType == "save")
414 handleToxSave(firstParam.toUtf8());
415
416 QObject::connect(a.get(), &QApplication::aboutToQuit, cleanup);
417
418 // Run
419 int errorcode = a->exec();
420
421 qDebug() << "Exit with status" << errorcode;
422 return errorcode;
423 }
424
425 // Missing in libxccrt.so function __cxa_deleted_virtual, see FreeBSD Bug#200863
__cxa_deleted_virtual()426 extern "C" void __cxa_deleted_virtual()
427 {
428 abort();
429 }
430
431