qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
main.cpp
Go to the documentation of this file.
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 "audio/audio.h"
21 #include "src/ipc.h"
22 #include "src/net/toxuri.h"
23 #include "src/nexus.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_UNIX)
48 #endif
49 
50 #ifdef LOG_TO_FILE
51 static QAtomicPointer<FILE> logFileFile = nullptr;
52 static QList<QByteArray>* logBuffer =
53  new QList<QByteArray>(); // Store log messages until log file opened
54 QMutex* logBufferMutex = new QMutex();
55 #endif
56 
57 void cleanup()
58 {
59  // force save early even though destruction saves, because Windows OS will
60  // close qTox before cleanup() is finished if logging out or shutting down,
61  // once the top level window has exited, which occurs in ~Widget within
62  // ~Nexus. Re-ordering Nexus destruction is not trivial.
63  auto& s = Settings::getInstance();
64  s.saveGlobal();
65  s.savePersonal();
66  s.sync();
67 
71  qDebug() << "Cleanup success";
72 
73 #ifdef LOG_TO_FILE
74 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
75  FILE* f = logFileFile.loadRelaxed();
76 #else
77  FILE* f = logFileFile.load();
78 #endif
79  if (f != nullptr) {
80  fclose(f);
81 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
82  logFileFile.storeRelaxed(nullptr); // atomically disable logging to file
83 #else
84  logFileFile.store(nullptr); // atomically disable logging to file
85 #endif
86  }
87 #endif
88 }
89 
90 void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QString& msg)
91 {
92  // Silence qWarning spam due to bug in QTextBrowser (trying to open a file for base64 images)
93  if (ctxt.function == QString("virtual bool QFSFileEngine::open(QIODevice::OpenMode)")
94  && msg == QString("QFSFileEngine::open: No file name specified"))
95  return;
96 
97  QRegExp snoreFilter{QStringLiteral("Snore::Notification.*was already closed")};
98  if (type == QtWarningMsg
99  && msg.contains(snoreFilter))
100  {
101  // snorenotify logs this when we call requestCloseNotification correctly. The behaviour still works, so we'll
102  // just mask the warning for now. The issue has been reported upstream:
103  // https://github.com/qTox/qTox/pull/6073#pullrequestreview-420748519
104  return;
105  }
106 
107  QString file = ctxt.file;
108  // We're not using QT_MESSAGELOG_FILE here, because that can be 0, NULL, or
109  // nullptr in release builds.
110  QString path = QString(__FILE__);
111  path = path.left(path.lastIndexOf('/') + 1);
112  if (file.startsWith(path)) {
113  file = file.mid(path.length());
114  }
115 
116  // Time should be in UTC to save user privacy on log sharing
117  QTime time = QDateTime::currentDateTime().toUTC().time();
118  QString LogMsg =
119  QString("[%1 UTC] %2:%3 : ").arg(time.toString("HH:mm:ss.zzz")).arg(file).arg(ctxt.line);
120  switch (type) {
121  case QtDebugMsg:
122  LogMsg += "Debug";
123  break;
124  case QtInfoMsg:
125  LogMsg += "Info";
126  break;
127  case QtWarningMsg:
128  LogMsg += "Warning";
129  break;
130  case QtCriticalMsg:
131  LogMsg += "Critical";
132  break;
133  case QtFatalMsg:
134  LogMsg += "Fatal";
135  break;
136  default:
137  break;
138  }
139 
140  LogMsg += ": " + msg + "\n";
141  QByteArray LogMsgBytes = LogMsg.toUtf8();
142  fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), stderr);
143 
144 #ifdef LOG_TO_FILE
145 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
146  FILE* logFilePtr = logFileFile.loadRelaxed(); // atomically load the file pointer
147 #else
148  FILE* logFilePtr = logFileFile.load(); // atomically load the file pointer
149 #endif
150  if (!logFilePtr) {
151  logBufferMutex->lock();
152  if (logBuffer)
153  logBuffer->append(LogMsgBytes);
154 
155  logBufferMutex->unlock();
156  } else {
157  logBufferMutex->lock();
158  if (logBuffer) {
159  // empty logBuffer to file
160  foreach (QByteArray msg, *logBuffer)
161  fwrite(msg.constData(), 1, msg.size(), logFilePtr);
162 
163  delete logBuffer; // no longer needed
164  logBuffer = nullptr;
165  }
166  logBufferMutex->unlock();
167 
168  fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), logFilePtr);
169  fflush(logFilePtr);
170  }
171 #endif
172 }
173 
174 static std::unique_ptr<ToxURIDialog> uriDialog;
175 
176 static bool toxURIEventHandler(const QByteArray& eventData)
177 {
178  if (!eventData.startsWith("tox:")) {
179  return false;
180  }
181 
182  if (!uriDialog) {
183  return false;
184  }
185 
186  uriDialog->handleToxURI(eventData);
187  return true;
188 }
189 
190 int main(int argc, char* argv[])
191 {
192 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
193  QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
194  QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
195 #endif
196 
197  qInstallMessageHandler(logMessageHandler);
198 
199  std::unique_ptr<QApplication> a(new QApplication(argc, argv));
200 
201 #if defined(Q_OS_UNIX)
202  // PosixSignalNotifier is used only for terminating signals,
203  // so it's connected directly to quit() without any filtering.
205  a.get(), &QApplication::quit);
207 #endif
208 
209  a->setApplicationName("qTox");
210 #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
211  a->setDesktopFileName("io.github.qtox.qTox");
212 #endif
213  a->setApplicationVersion("\nGit commit: " + QString(GIT_VERSION));
214 
215  // Install Unicode 6.1 supporting font
216  // Keep this as close to the beginning of `main()` as possible, otherwise
217  // on systems that have poor support for Unicode qTox will look bad.
218  if (QFontDatabase::addApplicationFont("://font/DejaVuSans.ttf") == -1) {
219  qWarning() << "Couldn't load font";
220  }
221 
222  Settings& settings = Settings::getInstance();
223  QString locale = settings.getTranslation();
224  // We need to init the resources in the translations_library explicitely.
225  // See https://doc.qt.io/qt-5/resources.html#using-resources-in-a-library
226  Q_INIT_RESOURCE(translations);
227  Translator::translate(locale);
228 
229  // Process arguments
230  QCommandLineParser parser;
231  parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION));
232  parser.addHelpOption();
233  parser.addVersionOption();
234  parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse"));
235  parser.addOption(
236  QCommandLineOption(QStringList() << "p"
237  << "profile",
238  QObject::tr("Starts new instance and loads specified profile."),
239  QObject::tr("profile")));
240  parser.addOption(
241  QCommandLineOption(QStringList() << "l"
242  << "login",
243  QObject::tr("Starts new instance and opens the login screen.")));
244  parser.addOption(QCommandLineOption(QStringList() << "I"
245  << "IPv6",
246  QObject::tr("Sets IPv6 <on>/<off>. Default is ON."),
247  QObject::tr("on/off")));
248  parser.addOption(QCommandLineOption(QStringList() << "U"
249  << "UDP",
250  QObject::tr("Sets UDP <on>/<off>. Default is ON."),
251  QObject::tr("on/off")));
252  parser.addOption(
253  QCommandLineOption(QStringList() << "L"
254  << "LAN",
255  QObject::tr(
256  "Sets LAN discovery <on>/<off>. UDP off overrides. Default is ON."),
257  QObject::tr("on/off")));
258  parser.addOption(QCommandLineOption(QStringList() << "P"
259  << "proxy",
260  QObject::tr("Sets proxy settings. Default is NONE."),
261  QObject::tr("(SOCKS5/HTTP/NONE):(ADDRESS):(PORT)")));
262  parser.process(*a);
263 
264  uint32_t profileId = settings.getCurrentProfileId();
265  IPC ipc(profileId);
266  if (ipc.isAttached()) {
267  QObject::connect(&settings, &Settings::currentProfileIdChanged, &ipc, &IPC::setProfileId);
268  } else {
269  qWarning() << "Can't init IPC, maybe we're in a jail? Continuing with reduced multi-client functionality.";
270  }
271 
272  // For the auto-updater
273  if (sodium_init() < 0) {
274  qCritical() << "Can't init libsodium";
275  return EXIT_FAILURE;
276  }
277 
278 #ifdef LOG_TO_FILE
279  QString logFileDir = settings.getPaths().getAppCacheDirPath();
280  QDir(logFileDir).mkpath(".");
281 
282  QString logfile = logFileDir + "qtox.log";
283  FILE* mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a");
284 
285  // Trim log file if over 1MB
286  if (QFileInfo(logfile).size() > 1000000) {
287  qDebug() << "Log file over 1MB, rotating...";
288 
289  // close old logfile (need for windows)
290  if (mainLogFilePtr)
291  fclose(mainLogFilePtr);
292 
293  QDir dir(logFileDir);
294 
295  // Check if log.1 already exists, and if so, delete it
296  if (dir.remove(logFileDir + "qtox.log.1"))
297  qDebug() << "Removed old log successfully";
298  else
299  qWarning() << "Unable to remove old log file";
300 
301  if (!dir.rename(logFileDir + "qtox.log", logFileDir + "qtox.log.1"))
302  qCritical() << "Unable to move logs";
303 
304  // open a new logfile
305  mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a");
306  }
307 
308  if (!mainLogFilePtr)
309  qCritical() << "Couldn't open logfile" << logfile;
310 
311 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
312  logFileFile.storeRelaxed(mainLogFilePtr); // atomically set the logFile
313 #else
314  logFileFile.store(mainLogFilePtr); // atomically set the logFile
315 #endif
316 #endif
317 
318  // Windows platform plugins DLL hell fix
319  QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
320  a->addLibraryPath("platforms");
321 
322  qDebug() << "commit: " << GIT_VERSION;
323 
324  QString profileName;
325  bool autoLogin = settings.getAutoLogin();
326 
327  uint32_t ipcDest = 0;
328  bool doIpc = ipc.isAttached();
329  QString eventType, firstParam;
330  if (parser.isSet("p")) {
331  profileName = parser.value("p");
332  if (!Profile::exists(profileName)) {
333  qWarning() << "-p profile" << profileName + ".tox"
334  << "doesn't exist, opening login screen";
335  doIpc = false;
336  autoLogin = false;
337  } else {
338  ipcDest = Settings::makeProfileId(profileName);
339  autoLogin = true;
340  }
341  } else if (parser.isSet("l")) {
342  doIpc = false;
343  autoLogin = false;
344  } else {
345  profileName = settings.getCurrentProfile();
346  }
347 
348  if (parser.positionalArguments().empty()) {
349  eventType = "activate";
350  } else {
351  firstParam = parser.positionalArguments()[0];
352  // Tox URIs. If there's already another qTox instance running, we ask it to handle the URI
353  // and we exit
354  // Otherwise we start a new qTox instance and process it ourselves
355  if (firstParam.startsWith("tox:")) {
356  eventType = "uri";
357  } else if (firstParam.endsWith(".tox")) {
358  eventType = "save";
359  } else {
360  qCritical() << "Invalid argument";
361  return EXIT_FAILURE;
362  }
363  }
364 
365  if (doIpc && !ipc.isCurrentOwner()) {
366  time_t event = ipc.postEvent(eventType, firstParam.toUtf8(), ipcDest);
367  // If someone else processed it, we're done here, no need to actually start qTox
368  if (ipc.waitUntilAccepted(event, 2)) {
369  if (eventType == "activate") {
370  qDebug()
371  << "Another qTox instance is already running. If you want to start a second "
372  "instance, please open login screen (qtox -l) or start with a profile (qtox "
373  "-p <profile name>).";
374  } else {
375  qDebug() << "Event" << eventType << "was handled by other client.";
376  }
377  return EXIT_SUCCESS;
378  }
379  }
380 
381  if (!Settings::verifyProxySettings(parser)) {
382  return -1;
383  }
384 
385  // TODO(sudden6): remove once we get rid of Nexus
386  Nexus& nexus = Nexus::getInstance();
387  // TODO(kriby): Consider moving application initializing variables into a globalSettings object
388  // note: Because Settings is shouldering global settings as well as model specific ones it
389  // cannot be integrated into a central model object yet
390  nexus.setSettings(&settings);
391 
392  // Autologin
393  // TODO (kriby): Shift responsibility of linking views to model objects from nexus
394  // Further: generate view instances separately (loginScreen, mainGUI, audio)
395  Profile* profile = nullptr;
396  if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) {
397  profile = Profile::loadProfile(profileName, QString(), settings, &parser);
398  if (!profile) {
399  QMessageBox::information(nullptr, QObject::tr("Error"),
400  QObject::tr("Failed to load profile automatically."));
401  }
402  }
403  if (profile) {
404  nexus.bootstrapWithProfile(profile);
405  } else {
406  nexus.setParser(&parser);
407  int returnval = nexus.showLogin(profileName);
408  if (returnval == QDialog::Rejected) {
409  return -1;
410  }
411  profile = nexus.getProfile();
412  }
413 
414  uriDialog = std::unique_ptr<ToxURIDialog>(new ToxURIDialog(nullptr, profile->getCore()));
415 
416  if (ipc.isAttached()) {
417  // Start to accept Inter-process communication
418  ipc.registerEventHandler("uri", &toxURIEventHandler);
421  }
422 
423  // Event was not handled by already running instance therefore we handle it ourselves
424  if (eventType == "uri") {
425  uriDialog->handleToxURI(firstParam.toUtf8());
426  } else if (eventType == "save") {
427  handleToxSave(firstParam.toUtf8());
428  }
429 
430  QObject::connect(a.get(), &QApplication::aboutToQuit, cleanup);
431 
432  // Run
433  int errorcode = a->exec();
434 
435  qDebug() << "Exit with status" << errorcode;
436  return errorcode;
437 }
profile.h
Settings
Definition: settings.h:51
Nexus::showLogin
int showLogin(const QString &profileName=QString())
Hides the main GUI, delete the profile, and shows the login screen.
Definition: nexus.cpp:153
Nexus::setSettings
void setSettings(Settings *settings)
Definition: nexus.cpp:194
ToxURIDialog
Definition: toxuri.h:31
Settings::getAutoLogin
bool getAutoLogin() const
Definition: settings.cpp:2153
settings.h
IPC::isCurrentOwner
bool isCurrentOwner()
Definition: ipc.cpp:176
loginscreen.h
Profile
Handles all qTox internal paths.
Definition: profile.h:42
IPC::isAttached
bool isAttached() const
Definition: ipc.cpp:235
Settings::verifyProxySettings
static bool verifyProxySettings(const QCommandLineParser &parser)
Definition: settings.cpp:284
Settings::getCurrentProfileId
uint32_t getCurrentProfileId() const
Definition: settings.cpp:1216
HistMessageContentType::file
@ file
IPC::setProfileId
void setProfileId(uint32_t profileId)
Definition: ipc.cpp:240
Translator::translate
static void translate(const QString &localeName)
Loads the translations according to the settings or locale.
Definition: translator.cpp:39
toxsave.h
Settings::makeProfileId
static uint32_t makeProfileId(const QString &profile)
Definition: settings.cpp:827
QList
Definition: friendlist.h:25
Nexus::getProfile
static Profile * getProfile()
Get current user profile.
Definition: nexus.cpp:290
Settings::currentProfileIdChanged
void currentProfileIdChanged(quint32 id)
Profile::getCore
Core & getCore() const
Definition: profile.cpp:428
Profile::exists
static bool exists(QString name)
Definition: profile.cpp:804
posixsignalnotifier.h
toxuri.h
IPC::waitUntilAccepted
bool waitUntilAccepted(time_t time, int32_t timeout=-1)
Definition: ipc.cpp:218
camerasource.h
widget.h
toxSaveEventHandler
bool toxSaveEventHandler(const QByteArray &eventData)
Definition: toxsave.cpp:25
Settings::getPaths
Paths & getPaths()
Definition: settings.cpp:834
CameraSource::destroyInstance
static void destroyInstance()
Definition: camerasource.cpp:135
IPC::registerEventHandler
void registerEventHandler(const QString &name, IPCEventHandler handler)
Register a handler for an IPC event.
Definition: ipc.cpp:192
ipc.h
Settings::getInstance
static Settings & getInstance()
Returns the singleton instance.
Definition: settings.cpp:88
PosixSignalNotifier::watchCommonTerminatingSignals
static void watchCommonTerminatingSignals()
Definition: posixsignalnotifier.cpp:103
Nexus
Definition: nexus.h:43
PosixSignalNotifier::globalInstance
static PosixSignalNotifier & globalInstance()
Definition: posixsignalnotifier.cpp:108
Paths::getAppCacheDirPath
QString getAppCacheDirPath() const
Get path to directory, where the application cache are stored.
Definition: paths.cpp:346
Nexus::bootstrapWithProfile
void bootstrapWithProfile(Profile *p)
Definition: nexus.cpp:180
Profile::loadProfile
static Profile * loadProfile(const QString &name, const QString &password, Settings &settings, const QCommandLineParser *parser)
Locks and loads an existing profile and creates the associate Core* instance.
Definition: profile.cpp:313
logMessageHandler
void logMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, const QString &msg)
Definition: main.cpp:90
PosixSignalNotifier::activated
void activated(int signal)
Nexus::destroyInstance
static void destroyInstance()
Definition: nexus.cpp:267
Settings::getCurrentProfile
QString getCurrentProfile() const
Definition: settings.cpp:1210
Settings::getTranslation
QString getTranslation() const
Definition: settings.cpp:1106
IPC
Inter-process communication.
Definition: ipc.h:34
Nexus::getInstance
static Nexus & getInstance()
Returns the singleton instance.
Definition: nexus.cpp:259
IPC::postEvent
time_t postEvent(const QString &name, const QByteArray &data=QByteArray(), uint32_t dest=0)
Post IPC event.
Definition: ipc.cpp:136
Nexus::setParser
void setParser(QCommandLineParser *parser)
Definition: nexus.cpp:331
Settings::destroyInstance
static void destroyInstance()
Definition: settings.cpp:96
Profile::isEncrypted
bool isEncrypted() const
Checks, if profile has a password.
Definition: profile.cpp:814
translator.h
nexus.h
cleanup
void cleanup()
Definition: main.cpp:57
main
int main(int argc, char *argv[])
Definition: main.cpp:190
handleToxSave
bool handleToxSave(const QString &path)
Import new profile.
Definition: toxsave.cpp:41
toxActivateEventHandler
bool toxActivateEventHandler(const QByteArray &)
Definition: widget.cpp:83