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.
36 #if defined(Q_OS_UNIX)
37 #include <sys/resource.h>
39 #if !defined Q_OS_WIN && !defined Q_OS_HAIKU
41 #elif defined Q_OS_WIN && defined DISABLE_GUI
51 #include <QMessageBox>
54 #include <QPushButton>
55 #include <QSplashScreen>
60 Q_IMPORT_PLUGIN(QICOPlugin
)
61 #endif // QBT_STATIC_QT
64 // NoGUI-only includes
70 #include "stacktrace.h"
72 #include "stacktrace_win.h"
74 #include "stacktracedialog.h"
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"
88 #include "gui/utils.h"
92 void sigNormalHandler(int signum
);
94 void sigAbnormalHandler(int signum
);
96 // sys_signame[] is only defined in BSD
97 const char *const sysSigName
[] =
100 "", "", "SIGINT", "", "SIGILL", "", "SIGABRT_COMPAT", "", "SIGFPE", "",
101 "", "SIGSEGV", "", "", "", "SIGTERM", "", "", "", "",
102 "", "SIGBREAK", "SIGABRT", "", "", "", "", "", "", "",
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"
112 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
113 void reportToUser(const char *str
);
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();
129 int main(int argc
, char *argv
[])
131 #if defined(Q_OS_UNIX)
132 adjustFileDescriptorLimit();
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
);
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
)
167 throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
168 .arg(QLatin1String("-v (or --version)")));
175 displayUsage(QString::fromLocal8Bit(argv
[0]));
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();
186 if (!userAgreesWithLegalNotice())
188 #elif defined(Q_OS_WIN)
189 if (_isatty(_fileno(stdin
))
190 && _isatty(_fileno(stdout
))
191 && !userAgreesWithLegalNotice())
194 if (!params
.shouldDaemonize
195 && isatty(fileno(stdin
))
196 && isatty(fileno(stdout
))
197 && !userAgreesWithLegalNotice())
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)")));
215 qDebug("qBittorrent is already running for this user.");
217 QThread::msleep(300);
218 app
->sendParams(params
.paramList());
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
);
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
);
253 if (!Preferences::instance()->iconsInMenusEnabled())
254 app
->setAttribute(Qt::AA_DontShowIconsInMenus
);
259 handleChangedDefaults(DefaultPreferencesMode::Legacy
);
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
;
267 if (!upgrade(!params
.shouldDaemonize
268 && isatty(fileno(stdin
))
269 && isatty(fileno(stdout
)))) return EXIT_FAILURE
;
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.
292 qCritical("Something went wrong while daemonizing, exiting...");
296 #elif !defined(DISABLE_GUI)
297 if (!(params
.noSplash
|| Preferences::instance()->isSplashScreenDisabled()))
301 signal(SIGINT
, sigNormalHandler
);
302 signal(SIGTERM
, sigNormalHandler
);
304 signal(SIGABRT
, sigAbnormalHandler
);
305 signal(SIGSEGV
, sigAbnormalHandler
);
308 return app
->exec(params
.paramList());
310 catch (const CommandLineParameterError
&er
)
312 displayBadArgMessage(er
.message());
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
);
322 if (write(STDERR_FILENO
, str
, strLen
) < static_cast<ssize_t
>(strLen
))
324 const auto dummy
= write(STDOUT_FILENO
, str
, strLen
);
326 if (_write(STDERR_FILENO
, str
, strLen
) < static_cast<ssize_t
>(strLen
))
328 const auto dummy
= _write(STDOUT_FILENO
, str
, strLen
);
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";
341 reportToUser(sysSigName
[signum
]);
343 #endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
344 signal(signum
, SIG_DFL
);
345 qApp
->exit(); // unsafe, but exit anyway
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"
358 reportToUser(sigName
);
360 print_stacktrace(); // unsafe
363 #if defined Q_OS_WIN && !defined DISABLE_GUI
364 StacktraceDialog dlg
; // unsafe
365 dlg
.setStacktraceString(QLatin1String(sigName
), straceWin::getBacktrace());
369 signal(signum
, SIG_DFL
);
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
);
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
));
405 const QString errMsg
= QObject::tr("Bad command line: ") + u
'\n'
408 fprintf(stderr
, "%s", qUtf8Printable(errMsg
));
412 bool userAgreesWithLegalNotice()
414 Preferences
*const pref
= Preferences::instance();
415 Q_ASSERT(!pref
->getAcceptedLegal());
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'))
428 pref
->setAcceptedLegal(true);
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
));
440 if (msgBox
.clickedButton() == agreeButton
)
443 pref
->setAcceptedLegal(true);
446 #endif // DISABLE_GUI
451 #if defined(Q_OS_UNIX)
452 void adjustFileDescriptorLimit()
456 if (getrlimit(RLIMIT_NOFILE
, &limit
) != 0)
459 limit
.rlim_cur
= limit
.rlim_max
;
460 setrlimit(RLIMIT_NOFILE
, &limit
);