Sync translations from Transifex and run lupdate
[qBittorrent.git] / src / app / main.cpp
blob64a5fdd385108d9fc53e57b99292f5b12da6fdd8
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program 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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include <QtGlobal>
32 #include <csignal>
33 #include <cstdlib>
34 #include <memory>
36 #if defined(Q_OS_UNIX)
37 #include <sys/resource.h>
38 #endif
39 #if !defined Q_OS_WIN && !defined Q_OS_HAIKU
40 #include <unistd.h>
41 #elif defined Q_OS_WIN && defined DISABLE_GUI
42 #include <io.h>
43 #endif
45 #include <QDebug>
46 #include <QThread>
48 #ifndef DISABLE_GUI
49 // GUI-only includes
50 #include <QFont>
51 #include <QMessageBox>
52 #include <QPainter>
53 #include <QPen>
54 #include <QPushButton>
55 #include <QSplashScreen>
56 #include <QTimer>
58 #ifdef QBT_STATIC_QT
59 #include <QtPlugin>
60 Q_IMPORT_PLUGIN(QICOPlugin)
61 #endif // QBT_STATIC_QT
63 #else
64 // NoGUI-only includes
65 #include <cstdio>
66 #endif // DISABLE_GUI
68 #ifdef STACKTRACE
69 #ifdef Q_OS_UNIX
70 #include "stacktrace.h"
71 #else
72 #include "stacktrace_win.h"
73 #ifndef DISABLE_GUI
74 #include "stacktracedialog.h"
75 #endif // DISABLE_GUI
76 #endif // Q_OS_UNIX
77 #endif //STACKTRACE
79 #include "base/global.h"
80 #include "base/preferences.h"
81 #include "base/profile.h"
82 #include "base/version.h"
83 #include "application.h"
84 #include "cmdoptions.h"
85 #include "upgrade.h"
87 #ifndef DISABLE_GUI
88 #include "gui/utils.h"
89 #endif
91 // Signal handlers
92 void sigNormalHandler(int signum);
93 #ifdef STACKTRACE
94 void sigAbnormalHandler(int signum);
95 #endif
96 // sys_signame[] is only defined in BSD
97 const char *const sysSigName[] =
99 #if defined(Q_OS_WIN)
100 "", "", "SIGINT", "", "SIGILL", "", "SIGABRT_COMPAT", "", "SIGFPE", "",
101 "", "SIGSEGV", "", "", "", "SIGTERM", "", "", "", "",
102 "", "SIGBREAK", "SIGABRT", "", "", "", "", "", "", "",
103 "", ""
104 #else
105 "", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL",
106 "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP",
107 "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO",
108 "SIGPWR", "SIGUNUSED"
109 #endif
112 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
113 void reportToUser(const char *str);
114 #endif
116 void displayVersion();
117 bool userAgreesWithLegalNotice();
118 void displayBadArgMessage(const QString &message);
120 #if !defined(DISABLE_GUI)
121 void showSplashScreen();
122 #endif // DISABLE_GUI
124 #if defined(Q_OS_UNIX)
125 void adjustFileDescriptorLimit();
126 #endif
128 // Main
129 int main(int argc, char *argv[])
131 #if defined(Q_OS_UNIX)
132 adjustFileDescriptorLimit();
133 #endif
135 // We must save it here because QApplication constructor may change it
136 bool isOneArg = (argc == 2);
138 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && !defined(DISABLE_GUI)
139 // Attribute Qt::AA_EnableHighDpiScaling must be set before QCoreApplication is created
140 if (qgetenv("QT_ENABLE_HIGHDPI_SCALING").isEmpty() && qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR").isEmpty())
141 Application::setAttribute(Qt::AA_EnableHighDpiScaling, true);
142 // HighDPI scale factor policy must be set before QGuiApplication is created
143 if (qgetenv("QT_SCALE_FACTOR_ROUNDING_POLICY").isEmpty())
144 Application::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
145 #endif
149 // Create Application
150 auto app = std::make_unique<Application>(argc, argv);
152 const QBtCommandLineParameters params = app->commandLineArgs();
153 if (!params.unknownParameter.isEmpty())
155 throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.",
156 "--random-parameter is an unknown command line parameter.")
157 .arg(params.unknownParameter));
159 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
160 if (params.showVersion)
162 if (isOneArg)
164 displayVersion();
165 return EXIT_SUCCESS;
167 throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
168 .arg(QLatin1String("-v (or --version)")));
170 #endif
171 if (params.showHelp)
173 if (isOneArg)
175 displayUsage(QString::fromLocal8Bit(argv[0]));
176 return EXIT_SUCCESS;
178 throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
179 .arg(QLatin1String("-h (or --help)")));
182 const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal();
183 if (firstTimeUser)
185 #ifndef DISABLE_GUI
186 if (!userAgreesWithLegalNotice())
187 return EXIT_SUCCESS;
188 #elif defined(Q_OS_WIN)
189 if (_isatty(_fileno(stdin))
190 && _isatty(_fileno(stdout))
191 && !userAgreesWithLegalNotice())
192 return EXIT_SUCCESS;
193 #else
194 if (!params.shouldDaemonize
195 && isatty(fileno(stdin))
196 && isatty(fileno(stdout))
197 && !userAgreesWithLegalNotice())
198 return EXIT_SUCCESS;
199 #endif
201 setCurrentMigrationVersion();
204 // Check if qBittorrent is already running for this user
205 if (app->isRunning())
207 #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
208 if (params.shouldDaemonize)
210 throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.")
211 .arg(QLatin1String("-d (or --daemon)")));
213 else
214 #endif
215 qDebug("qBittorrent is already running for this user.");
217 QThread::msleep(300);
218 app->sendParams(params.paramList());
220 return EXIT_SUCCESS;
223 #if defined(Q_OS_WIN)
224 // This affects only Windows apparently and Qt5.
225 // When QNetworkAccessManager is instantiated it regularly starts polling
226 // the network interfaces to see what's available and their status.
227 // This polling creates jitter and high ping with wifi interfaces.
228 // So here we disable it for lack of better measure.
229 // It will also spew this message in the console: QObject::startTimer: Timers cannot have negative intervals
230 // For more info see:
231 // 1. https://github.com/qbittorrent/qBittorrent/issues/4209
232 // 2. https://bugreports.qt.io/browse/QTBUG-40332
233 // 3. https://bugreports.qt.io/browse/QTBUG-46015
235 qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
236 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && !defined(DISABLE_GUI)
237 // this is the default in Qt6
238 app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
239 #endif
240 #endif // Q_OS_WIN
242 #if defined(Q_OS_MACOS)
243 // Since Apple made difficult for users to set PATH, we set here for convenience.
244 // Users are supposed to install Homebrew Python for search function.
245 // For more info see issue #5571.
246 QByteArray path = "/usr/local/bin:";
247 path += qgetenv("PATH");
248 qputenv("PATH", path.constData());
250 // On OS X the standard is to not show icons in the menus
251 app->setAttribute(Qt::AA_DontShowIconsInMenus);
252 #else
253 if (!Preferences::instance()->iconsInMenusEnabled())
254 app->setAttribute(Qt::AA_DontShowIconsInMenus);
255 #endif
257 if (!firstTimeUser)
259 handleChangedDefaults(DefaultPreferencesMode::Legacy);
261 #ifndef DISABLE_GUI
262 if (!upgrade()) return EXIT_FAILURE;
263 #elif defined(Q_OS_WIN)
264 if (!upgrade(_isatty(_fileno(stdin))
265 && _isatty(_fileno(stdout)))) return EXIT_FAILURE;
266 #else
267 if (!upgrade(!params.shouldDaemonize
268 && isatty(fileno(stdin))
269 && isatty(fileno(stdout)))) return EXIT_FAILURE;
270 #endif
272 else
274 handleChangedDefaults(DefaultPreferencesMode::Current);
277 #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
278 if (params.shouldDaemonize)
280 app.reset(); // Destroy current application
281 if (daemon(1, 0) == 0)
283 app = std::make_unique<Application>(argc, argv);
284 if (app->isRunning())
286 // Another instance had time to start.
287 return EXIT_FAILURE;
290 else
292 qCritical("Something went wrong while daemonizing, exiting...");
293 return EXIT_FAILURE;
296 #elif !defined(DISABLE_GUI)
297 if (!(params.noSplash || Preferences::instance()->isSplashScreenDisabled()))
298 showSplashScreen();
299 #endif
301 signal(SIGINT, sigNormalHandler);
302 signal(SIGTERM, sigNormalHandler);
303 #ifdef STACKTRACE
304 signal(SIGABRT, sigAbnormalHandler);
305 signal(SIGSEGV, sigAbnormalHandler);
306 #endif
308 return app->exec(params.paramList());
310 catch (const CommandLineParameterError &er)
312 displayBadArgMessage(er.message());
313 return EXIT_FAILURE;
317 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
318 void reportToUser(const char *str)
320 const size_t strLen = strlen(str);
321 #ifndef Q_OS_WIN
322 if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
324 const auto dummy = write(STDOUT_FILENO, str, strLen);
325 #else
326 if (_write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
328 const auto dummy = _write(STDOUT_FILENO, str, strLen);
329 #endif
330 Q_UNUSED(dummy);
333 #endif
335 void sigNormalHandler(int signum)
337 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
338 const char msg1[] = "Catching signal: ";
339 const char msg2[] = "\nExiting cleanly\n";
340 reportToUser(msg1);
341 reportToUser(sysSigName[signum]);
342 reportToUser(msg2);
343 #endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
344 signal(signum, SIG_DFL);
345 qApp->exit(); // unsafe, but exit anyway
348 #ifdef STACKTRACE
349 void sigAbnormalHandler(int signum)
351 const char *sigName = sysSigName[signum];
352 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
353 const char msg[] = "\n\n*************************************************************\n"
354 "Please file a bug report at http://bug.qbittorrent.org and provide the following information:\n\n"
355 "qBittorrent version: " QBT_VERSION "\n\n"
356 "Caught signal: ";
357 reportToUser(msg);
358 reportToUser(sigName);
359 reportToUser("\n");
360 print_stacktrace(); // unsafe
361 #endif
363 #if defined Q_OS_WIN && !defined DISABLE_GUI
364 StacktraceDialog dlg; // unsafe
365 dlg.setStacktraceString(QLatin1String(sigName), straceWin::getBacktrace());
366 dlg.exec();
367 #endif
369 signal(signum, SIG_DFL);
370 raise(signum);
372 #endif // STACKTRACE
374 #if !defined(DISABLE_GUI)
375 void showSplashScreen()
377 QPixmap splashImg(u":/icons/splash.png"_qs);
378 QPainter painter(&splashImg);
379 const auto version = QStringLiteral(QBT_VERSION);
380 painter.setPen(QPen(Qt::white));
381 painter.setFont(QFont(u"Arial"_qs, 22, QFont::Black));
382 painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
383 QSplashScreen *splash = new QSplashScreen(splashImg);
384 splash->show();
385 QTimer::singleShot(1500, splash, &QObject::deleteLater);
386 qApp->processEvents();
388 #endif // DISABLE_GUI
390 void displayVersion()
392 printf("%s %s\n", qUtf8Printable(qApp->applicationName()), QBT_VERSION);
395 void displayBadArgMessage(const QString &message)
397 const QString help = QObject::tr("Run application with -h option to read about command line parameters.");
398 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
399 QMessageBox msgBox(QMessageBox::Critical, QObject::tr("Bad command line"),
400 message + QLatin1Char('\n') + help, QMessageBox::Ok);
401 msgBox.show(); // Need to be shown or to moveToCenter does not work
402 msgBox.move(Utils::Gui::screenCenter(&msgBox));
403 msgBox.exec();
404 #else
405 const QString errMsg = QObject::tr("Bad command line: ") + u'\n'
406 + message + u'\n'
407 + help + u'\n';
408 fprintf(stderr, "%s", qUtf8Printable(errMsg));
409 #endif
412 bool userAgreesWithLegalNotice()
414 Preferences *const pref = Preferences::instance();
415 Q_ASSERT(!pref->getAcceptedLegal());
417 #ifdef DISABLE_GUI
418 const QString eula = QString::fromLatin1("\n*** %1 ***\n").arg(QObject::tr("Legal Notice"))
419 + QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + u"\n\n"
420 + QObject::tr("No further notices will be issued.") + u"\n\n"
421 + QObject::tr("Press %1 key to accept and continue...").arg(u"'y'"_qs) + u'\n';
422 printf("%s", qUtf8Printable(eula));
424 const char ret = getchar(); // Read pressed key
425 if ((ret == 'y') || (ret == 'Y'))
427 // Save the answer
428 pref->setAcceptedLegal(true);
429 return true;
431 #else
432 QMessageBox msgBox;
433 msgBox.setText(QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
434 msgBox.setWindowTitle(QObject::tr("Legal notice"));
435 msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
436 const QAbstractButton *agreeButton = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
437 msgBox.show(); // Need to be shown or to moveToCenter does not work
438 msgBox.move(Utils::Gui::screenCenter(&msgBox));
439 msgBox.exec();
440 if (msgBox.clickedButton() == agreeButton)
442 // Save the answer
443 pref->setAcceptedLegal(true);
444 return true;
446 #endif // DISABLE_GUI
448 return false;
451 #if defined(Q_OS_UNIX)
452 void adjustFileDescriptorLimit()
454 rlimit limit {};
456 if (getrlimit(RLIMIT_NOFILE, &limit) != 0)
457 return;
459 limit.rlim_cur = limit.rlim_max;
460 setrlimit(RLIMIT_NOFILE, &limit);
462 #endif