spacepaste

  1.  
  2. /*
  3. * Bittorrent Client using Qt and libtorrent.
  4. * Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
  5. * Copyright (C) 2006 Christophe Dumez
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. *
  21. * In addition, as a special exception, the copyright holders give permission to
  22. * link this program with the OpenSSL project's "OpenSSL" library (or with
  23. * modified versions of it that use the same license as the "OpenSSL" library),
  24. * and distribute the linked executables. You must obey the GNU General Public
  25. * License in all respects for all of the code used other than "OpenSSL". If you
  26. * modify file(s), you may extend this exception to your version of the file(s),
  27. * but you are not obligated to do so. If you do not wish to do so, delete this
  28. * exception statement from your version.
  29. *
  30. * Contact : chris@qbittorrent.org
  31. */
  32. #include <QDebug>
  33. #include <QScopedPointer>
  34. #include <QThread>
  35. #ifndef DISABLE_GUI
  36. // GUI-only includes
  37. #include <QFont>
  38. #include <QMessageBox>
  39. #include <QPainter>
  40. #include <QPen>
  41. #include <QPushButton>
  42. #include <QSplashScreen>
  43. #ifdef QBT_STATIC_QT
  44. #include <QtPlugin>
  45. Q_IMPORT_PLUGIN(QICOPlugin)
  46. #endif // QBT_STATIC_QT
  47. #else
  48. // NoGUI-only includes
  49. #include <cstdio>
  50. #ifdef Q_OS_UNIX
  51. #include "unistd.h"
  52. #endif
  53. #endif // DISABLE_GUI
  54. #ifdef Q_OS_UNIX
  55. #include <signal.h>
  56. //#include <execinfo.h>
  57. //#include "stacktrace.h"
  58. #endif // Q_OS_UNIX
  59. #ifdef STACKTRACE_WIN
  60. #include <signal.h>
  61. #include "stacktrace_win.h"
  62. #include "stacktrace_win_dlg.h"
  63. #endif //STACKTRACE_WIN
  64. #include <cstdlib>
  65. #include <iostream>
  66. #include "application.h"
  67. #include "base/profile.h"
  68. #include "base/utils/misc.h"
  69. #include "base/preferences.h"
  70. #include "cmdoptions.h"
  71. #include "upgrade.h"
  72. // Signal handlers
  73. #if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
  74. void sigNormalHandler(int signum);
  75. void sigAbnormalHandler(int signum);
  76. // sys_signame[] is only defined in BSD
  77. const char *sysSigName[] = {
  78. #if defined(Q_OS_WIN)
  79. "", "", "SIGINT", "", "SIGILL", "", "SIGABRT_COMPAT", "", "SIGFPE", "",
  80. "", "SIGSEGV", "", "", "", "SIGTERM", "", "", "", "",
  81. "", "SIGBREAK", "SIGABRT", "", "", "", "", "", "", "",
  82. "", ""
  83. #else
  84. "", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL",
  85. "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP",
  86. "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO",
  87. "SIGPWR", "SIGUNUSED"
  88. #endif
  89. };
  90. #endif
  91. #if !defined Q_OS_WIN && !defined Q_OS_HAIKU
  92. void reportToUser(const char* str);
  93. #endif
  94. void displayVersion();
  95. bool userAgreesWithLegalNotice();
  96. void displayBadArgMessage(const QString &message);
  97. #if !defined(DISABLE_GUI)
  98. void showSplashScreen();
  99. #if defined(Q_OS_UNIX)
  100. void setupDpi();
  101. #endif // Q_OS_UNIX
  102. #endif // DISABLE_GUI
  103. // Main
  104. int main(int argc, char *argv[])
  105. {
  106. // We must save it here because QApplication constructor may change it
  107. bool isOneArg = (argc == 2);
  108. #ifdef Q_OS_MAC
  109. // On macOS 10.12 Sierra, Apple changed the behaviour of CFPreferencesSetValue() https://bugreports.qt.io/browse/QTBUG-56344
  110. // Due to this, we have to move from native plist to IniFormat
  111. macMigratePlists();
  112. #endif
  113. #if !defined(DISABLE_GUI) && defined(Q_OS_UNIX)
  114. setupDpi();
  115. #endif
  116. try {
  117. // Create Application
  118. QString appId = QLatin1String("qBittorrent-") + Utils::Misc::getUserIDString();
  119. QScopedPointer<Application> app(new Application(appId, argc, argv));
  120. #ifndef DISABLE_GUI
  121. // after the application object creation because we need a profile to be set already
  122. // for the migration
  123. migrateRSS();
  124. #endif
  125. const QBtCommandLineParameters params = app->commandLineArgs();
  126. if (!params.unknownParameter.isEmpty()) {
  127. throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.",
  128. "--random-parameter is an unknown command line parameter.")
  129. .arg(params.unknownParameter));
  130. }
  131. #ifndef Q_OS_WIN
  132. if (params.showVersion) {
  133. if (isOneArg) {
  134. displayVersion();
  135. return EXIT_SUCCESS;
  136. }
  137. throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
  138. .arg(QLatin1String("-v (or --version)")));
  139. }
  140. #endif
  141. if (params.showHelp) {
  142. if (isOneArg) {
  143. displayUsage(argv[0]);
  144. return EXIT_SUCCESS;
  145. }
  146. throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
  147. .arg(QLatin1String("-h (or --help)")));
  148. }
  149. // Set environment variable
  150. if (!qputenv("QBITTORRENT", QBT_VERSION))
  151. std::cerr << "Couldn't set environment variable...\n";
  152. #ifndef DISABLE_GUI
  153. if (!userAgreesWithLegalNotice())
  154. return EXIT_SUCCESS;
  155. #else
  156. if (!params.shouldDaemonize
  157. && isatty(fileno(stdin))
  158. && isatty(fileno(stdout))
  159. && !userAgreesWithLegalNotice())
  160. return EXIT_SUCCESS;
  161. #endif
  162. // Check if qBittorrent is already running for this user
  163. if (app->isRunning()) {
  164. #ifdef DISABLE_GUI
  165. if (params.shouldDaemonize) {
  166. throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.")
  167. .arg(QLatin1String("-d (or --daemon)")));
  168. }
  169. else
  170. #endif
  171. qDebug("qBittorrent is already running for this user.");
  172. QThread::msleep(300);
  173. app->sendParams(params.paramList());
  174. return EXIT_SUCCESS;
  175. }
  176. #if defined(Q_OS_WIN)
  177. // This affects only Windows apparently and Qt5.
  178. // When QNetworkAccessManager is instantiated it regularly starts polling
  179. // the network interfaces to see what's available and their status.
  180. // This polling creates jitter and high ping with wifi interfaces.
  181. // So here we disable it for lack of better measure.
  182. // It will also spew this message in the console: QObject::startTimer: Timers cannot have negative intervals
  183. // For more info see:
  184. // 1. https://github.com/qbittorrent/qBittorrent/issues/4209
  185. // 2. https://bugreports.qt.io/browse/QTBUG-40332
  186. // 3. https://bugreports.qt.io/browse/QTBUG-46015
  187. qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
  188. #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
  189. // this is the default in Qt6
  190. app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
  191. #endif
  192. #endif
  193. #if defined(Q_OS_MAC)
  194. // Since Apple made difficult for users to set PATH, we set here for convenience.
  195. // Users are supposed to install Homebrew Python for search function.
  196. // For more info see issue #5571.
  197. QByteArray path = "/usr/local/bin:";
  198. path += qgetenv("PATH");
  199. qputenv("PATH", path.constData());
  200. // On OS X the standard is to not show icons in the menus
  201. app->setAttribute(Qt::AA_DontShowIconsInMenus);
  202. #endif
  203. #ifndef DISABLE_GUI
  204. if (!upgrade()) return EXIT_FAILURE;
  205. #else
  206. if (!upgrade(!params.shouldDaemonize
  207. && isatty(fileno(stdin))
  208. && isatty(fileno(stdout)))) return EXIT_FAILURE;
  209. #endif
  210. #ifdef DISABLE_GUI
  211. if (params.shouldDaemonize) {
  212. app.reset(); // Destroy current application
  213. if ((daemon(1, 0) == 0)) {
  214. app.reset(new Application(appId, argc, argv));
  215. if (app->isRunning()) {
  216. // Another instance had time to start.
  217. return EXIT_FAILURE;
  218. }
  219. }
  220. else {
  221. qCritical("Something went wrong while daemonizing, exiting...");
  222. return EXIT_FAILURE;
  223. }
  224. }
  225. #else
  226. if (!(params.noSplash || Preferences::instance()->isSplashScreenDisabled()))
  227. showSplashScreen();
  228. #endif
  229. #if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
  230. signal(SIGINT, sigNormalHandler);
  231. signal(SIGTERM, sigNormalHandler);
  232. signal(SIGABRT, sigAbnormalHandler);
  233. signal(SIGSEGV, sigAbnormalHandler);
  234. #endif
  235. return app->exec(params.paramList());
  236. }
  237. catch (CommandLineParameterError &er) {
  238. displayBadArgMessage(er.messageForUser());
  239. return EXIT_FAILURE;
  240. }
  241. }
  242. #if !defined Q_OS_WIN && !defined Q_OS_HAIKU
  243. void reportToUser(const char* str)
  244. {
  245. const size_t strLen = strlen(str);
  246. if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen)) {
  247. auto dummy = write(STDOUT_FILENO, str, strLen);
  248. Q_UNUSED(dummy);
  249. }
  250. }
  251. #endif
  252. #if defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
  253. void sigNormalHandler(int signum)
  254. {
  255. #if !defined Q_OS_WIN && !defined Q_OS_HAIKU
  256. const char msg1[] = "Catching signal: ";
  257. const char msg2[] = "\nExiting cleanly\n";
  258. reportToUser(msg1);
  259. reportToUser(sysSigName[signum]);
  260. reportToUser(msg2);
  261. #endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
  262. signal(signum, SIG_DFL);
  263. qApp->exit(); // unsafe, but exit anyway
  264. }
  265. void sigAbnormalHandler(int signum)
  266. {
  267. const char *sigName = sysSigName[signum];
  268. #if !defined Q_OS_WIN && !defined Q_OS_HAIKU
  269. const char msg[] = "\n\n*************************************************************\n"
  270. "Please file a bug report at http://bug.qbittorrent.org and provide the following information:\n\n"
  271. "qBittorrent version: " QBT_VERSION "\n\n"
  272. "Caught signal: ";
  273. reportToUser(msg);
  274. reportToUser(sigName);
  275. reportToUser("\n");
  276. // print_stacktrace(); // unsafe
  277. #endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
  278. #ifdef STACKTRACE_WIN
  279. StraceDlg dlg; // unsafe
  280. dlg.setStacktraceString(QLatin1String(sigName), straceWin::getBacktrace());
  281. dlg.exec();
  282. #endif // STACKTRACE_WIN
  283. signal(signum, SIG_DFL);
  284. raise(signum);
  285. }
  286. #endif // defined(Q_OS_UNIX) || defined(STACKTRACE_WIN)
  287. #if !defined(DISABLE_GUI)
  288. void showSplashScreen()
  289. {
  290. QPixmap splash_img(":/icons/skin/splash.png");
  291. QPainter painter(&splash_img);
  292. QString version = QBT_VERSION;
  293. painter.setPen(QPen(Qt::white));
  294. painter.setFont(QFont("Arial", 22, QFont::Black));
  295. painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
  296. QSplashScreen *splash = new QSplashScreen(splash_img);
  297. splash->show();
  298. QTimer::singleShot(1500, splash, SLOT(deleteLater()));
  299. qApp->processEvents();
  300. }
  301. #if defined(Q_OS_UNIX)
  302. void setupDpi()
  303. {
  304. if (qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR").isEmpty())
  305. qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
  306. }
  307. #endif // Q_OS_UNIX
  308. #endif // DISABLE_GUI
  309. void displayVersion()
  310. {
  311. std::cout << qPrintable(qApp->applicationName()) << " " << QBT_VERSION << std::endl;
  312. }
  313. void displayBadArgMessage(const QString& message)
  314. {
  315. QString help = QObject::tr("Run application with -h option to read about command line parameters.");
  316. #ifdef Q_OS_WIN
  317. QMessageBox msgBox(QMessageBox::Critical, QObject::tr("Bad command line"),
  318. message + QLatin1Char('\n') + help, QMessageBox::Ok);
  319. msgBox.show(); // Need to be shown or to moveToCenter does not work
  320. msgBox.move(Utils::Misc::screenCenter(&msgBox));
  321. msgBox.exec();
  322. #else
  323. std::cerr << qPrintable(QObject::tr("Bad command line: "));
  324. std::cerr << qPrintable(message) << std::endl;
  325. std::cerr << qPrintable(help) << std::endl;
  326. #endif
  327. }
  328. bool userAgreesWithLegalNotice()
  329. {
  330. Preferences* const pref = Preferences::instance();
  331. if (pref->getAcceptedLegal()) // Already accepted once
  332. return true;
  333. #ifdef DISABLE_GUI
  334. std::cout << std::endl << "*** " << qPrintable(QObject::tr("Legal Notice")) << " ***" << std::endl;
  335. std::cout << qPrintable(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.")) << std::endl << std::endl;
  336. std::cout << qPrintable(QObject::tr("Press %1 key to accept and continue...").arg("'y'")) << std::endl;
  337. char ret = getchar(); // Read pressed key
  338. if (ret == 'y' || ret == 'Y') {
  339. // Save the answer
  340. pref->setAcceptedLegal(true);
  341. return true;
  342. }
  343. #else
  344. QMessageBox msgBox;
  345. 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."));
  346. msgBox.setWindowTitle(QObject::tr("Legal notice"));
  347. msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
  348. QAbstractButton *agree_button = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
  349. msgBox.show(); // Need to be shown or to moveToCenter does not work
  350. msgBox.move(Utils::Misc::screenCenter(&msgBox));
  351. msgBox.exec();
  352. if (msgBox.clickedButton() == agree_button) {
  353. // Save the answer
  354. pref->setAcceptedLegal(true);
  355. return true;
  356. }
  357. #endif
  358. return false;
  359. }
  360.