main.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. // SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
  3. #ifndef USE_EXTERNAL_SINGLEAPPLICATION
  4. #include "singleapplication.h"
  5. #else
  6. #include "QtSolutions/qtsingleapplication.h"
  7. #endif
  8. #include "abstractlogger.h"
  9. #include "src/cli/commandlineparser.h"
  10. #include "src/config/cacheutils.h"
  11. #include "src/config/styleoverride.h"
  12. #include "src/core/capturerequest.h"
  13. #include "src/core/flameshot.h"
  14. #include "src/core/flameshotdaemon.h"
  15. #include "src/utils/confighandler.h"
  16. #include "src/utils/filenamehandler.h"
  17. #include "src/utils/pathinfo.h"
  18. #include "src/utils/valuehandler.h"
  19. #include <QApplication>
  20. #include <QDir>
  21. #include <QLibraryInfo>
  22. #include <QSharedMemory>
  23. #include <QTimer>
  24. #include <QTranslator>
  25. #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX)
  26. #include "abstractlogger.h"
  27. #include "src/core/flameshotdbusadapter.h"
  28. #include <QApplication>
  29. #include <QDBusConnection>
  30. #include <QDBusMessage>
  31. #include <desktopinfo.h>
  32. #endif
  33. #ifdef Q_OS_LINUX
  34. // source: https://github.com/ksnip/ksnip/issues/416
  35. void wayland_hacks()
  36. {
  37. // Workaround to https://github.com/ksnip/ksnip/issues/416
  38. DesktopInfo info;
  39. if (info.windowManager() == DesktopInfo::GNOME) {
  40. qputenv("QT_QPA_PLATFORM", "xcb");
  41. }
  42. }
  43. #endif
  44. void requestCaptureAndWait(const CaptureRequest& req)
  45. {
  46. Flameshot* flameshot = Flameshot::instance();
  47. flameshot->requestCapture(req);
  48. QObject::connect(flameshot, &Flameshot::captureTaken, [&](const QPixmap&) {
  49. #if defined(Q_OS_MACOS)
  50. // Only useful on MacOS because each instance hosts its own widgets
  51. if (!FlameshotDaemon::isThisInstanceHostingWidgets()) {
  52. qApp->exit(0);
  53. }
  54. #else
  55. // if this instance is not daemon, make sure it exit after caputre finish
  56. if (FlameshotDaemon::instance() == nullptr && !Flameshot::instance()->haveExternalWidget()) {
  57. qApp->exit(0);
  58. }
  59. #endif
  60. });
  61. QObject::connect(flameshot, &Flameshot::captureFailed, []() {
  62. AbstractLogger::info() << "Screenshot aborted.";
  63. qApp->exit(1);
  64. });
  65. qApp->exec();
  66. }
  67. QSharedMemory* guiMutexLock()
  68. {
  69. QString key = "org.flameshot.Flameshot-" APP_VERSION;
  70. auto* shm = new QSharedMemory(key);
  71. #ifdef Q_OS_UNIX
  72. // Destroy shared memory if the last instance crashed on Unix
  73. shm->attach();
  74. delete shm;
  75. shm = new QSharedMemory(key);
  76. #endif
  77. if (!shm->create(1)) {
  78. return nullptr;
  79. }
  80. return shm;
  81. }
  82. QTranslator translator, qtTranslator;
  83. void configureApp(bool gui)
  84. {
  85. if (gui) {
  86. QApplication::setStyle(new StyleOverride);
  87. }
  88. // Configure translations
  89. for (const QString& path : PathInfo::translationsPaths()) {
  90. bool match = translator.load(QLocale(),
  91. QStringLiteral("Internationalization"),
  92. QStringLiteral("_"),
  93. path);
  94. if (match) {
  95. break;
  96. }
  97. }
  98. qtTranslator.load(QLocale::system(),
  99. "qt",
  100. "_",
  101. QLibraryInfo::location(QLibraryInfo::TranslationsPath));
  102. auto app = QCoreApplication::instance();
  103. app->installTranslator(&translator);
  104. app->installTranslator(&qtTranslator);
  105. app->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
  106. }
  107. // TODO find a way so we don't have to do this
  108. /// Recreate the application as a QApplication
  109. void reinitializeAsQApplication(int& argc, char* argv[])
  110. {
  111. delete QCoreApplication::instance();
  112. new QApplication(argc, argv);
  113. configureApp(true);
  114. }
  115. int main(int argc, char* argv[])
  116. {
  117. #ifdef Q_OS_LINUX
  118. wayland_hacks();
  119. #endif
  120. // required for the button serialization
  121. // TODO: change to QVector in v1.0
  122. qRegisterMetaTypeStreamOperators<QList<int>>("QList<int>");
  123. QCoreApplication::setApplicationVersion(APP_VERSION);
  124. QCoreApplication::setApplicationName(QStringLiteral("flameshot"));
  125. QCoreApplication::setOrganizationName(QStringLiteral("flameshot"));
  126. // no arguments, just launch Flameshot
  127. if (argc == 1) {
  128. #ifndef USE_EXTERNAL_SINGLEAPPLICATION
  129. SingleApplication app(argc, argv);
  130. #else
  131. QtSingleApplication app(argc, argv);
  132. #endif
  133. configureApp(true);
  134. auto c = Flameshot::instance();
  135. FlameshotDaemon::start();
  136. #if !(defined(Q_OS_MACOS) || defined(Q_OS_WIN))
  137. new FlameshotDBusAdapter(c);
  138. QDBusConnection dbus = QDBusConnection::sessionBus();
  139. if (!dbus.isConnected()) {
  140. AbstractLogger::error()
  141. << QObject::tr("Unable to connect via DBus");
  142. }
  143. dbus.registerObject(QStringLiteral("/"), c);
  144. dbus.registerService(QStringLiteral("org.flameshot.Flameshot"));
  145. #endif
  146. return qApp->exec();
  147. }
  148. #if !defined(Q_OS_WIN)
  149. /*--------------|
  150. * CLI parsing |
  151. * ------------*/
  152. new QCoreApplication(argc, argv);
  153. configureApp(false);
  154. CommandLineParser parser;
  155. // Add description
  156. parser.setDescription(
  157. QObject::tr("Powerful yet simple to use screenshot software."));
  158. parser.setGeneralErrorMessage(QObject::tr("See") + " flameshot --help.");
  159. // Arguments
  160. CommandArgument fullArgument(
  161. QStringLiteral("full"),
  162. QObject::tr("Capture screenshot of all monitors at the same time."));
  163. CommandArgument launcherArgument(QStringLiteral("launcher"),
  164. QObject::tr("Open the capture launcher."));
  165. CommandArgument guiArgument(
  166. QStringLiteral("gui"),
  167. QObject::tr("Start a manual capture in GUI mode."));
  168. CommandArgument configArgument(QStringLiteral("config"),
  169. QObject::tr("Configure") + " flameshot.");
  170. CommandArgument screenArgument(
  171. QStringLiteral("screen"),
  172. QObject::tr("Capture a screenshot of the specified monitor."));
  173. // Options
  174. CommandOption pathOption(
  175. { "p", "path" },
  176. QObject::tr("Existing directory or new file to save to"),
  177. QStringLiteral("path"));
  178. CommandOption clipboardOption(
  179. { "c", "clipboard" }, QObject::tr("Save the capture to the clipboard"));
  180. CommandOption pinOption("pin",
  181. QObject::tr("Pin the capture to the screen"));
  182. CommandOption uploadOption({ "u", "upload" },
  183. QObject::tr("Upload screenshot"));
  184. CommandOption delayOption({ "d", "delay" },
  185. QObject::tr("Delay time in milliseconds"),
  186. QStringLiteral("milliseconds"));
  187. CommandOption useLastRegionOption(
  188. "last-region",
  189. QObject::tr("Repeat screenshot with previously selected region"));
  190. CommandOption regionOption("region",
  191. QObject::tr("Screenshot region to select"),
  192. QStringLiteral("WxH+X+Y or string"));
  193. CommandOption filenameOption({ "f", "filename" },
  194. QObject::tr("Set the filename pattern"),
  195. QStringLiteral("pattern"));
  196. CommandOption acceptOnSelectOption(
  197. { "s", "accept-on-select" },
  198. QObject::tr("Accept capture as soon as a selection is made"));
  199. CommandOption trayOption({ "t", "trayicon" },
  200. QObject::tr("Enable or disable the trayicon"),
  201. QStringLiteral("bool"));
  202. CommandOption autostartOption(
  203. { "a", "autostart" },
  204. QObject::tr("Enable or disable run at startup"),
  205. QStringLiteral("bool"));
  206. CommandOption checkOption(
  207. "check", QObject::tr("Check the configuration for errors"));
  208. CommandOption showHelpOption(
  209. { "s", "showhelp" },
  210. QObject::tr("Show the help message in the capture mode"),
  211. QStringLiteral("bool"));
  212. CommandOption mainColorOption({ "m", "maincolor" },
  213. QObject::tr("Define the main UI color"),
  214. QStringLiteral("color-code"));
  215. CommandOption contrastColorOption(
  216. { "k", "contrastcolor" },
  217. QObject::tr("Define the contrast UI color"),
  218. QStringLiteral("color-code"));
  219. CommandOption rawImageOption({ "r", "raw" },
  220. QObject::tr("Print raw PNG capture"));
  221. CommandOption selectionOption(
  222. { "g", "print-geometry" },
  223. QObject::tr("Print geometry of the selection in the format WxH+X+Y. Does "
  224. "nothing if raw is specified"));
  225. CommandOption screenNumberOption(
  226. { "n", "number" },
  227. QObject::tr("Define the screen to capture (starting from 0)") + ",\n" +
  228. QObject::tr("default: screen containing the cursor"),
  229. QObject::tr("Screen number"),
  230. QStringLiteral("-1"));
  231. // Add checkers
  232. auto colorChecker = [](const QString& colorCode) -> bool {
  233. QColor parsedColor(colorCode);
  234. return parsedColor.isValid() && parsedColor.alphaF() == 1.0;
  235. };
  236. QString colorErr =
  237. QObject::tr("Invalid color, "
  238. "this flag supports the following formats:\n"
  239. "- #RGB (each of R, G, and B is a single hex digit)\n"
  240. "- #RRGGBB\n- #RRRGGGBBB\n"
  241. "- #RRRRGGGGBBBB\n"
  242. "- Named colors like 'blue' or 'red'\n"
  243. "You may need to escape the '#' sign as in '\\#FFF'");
  244. const QString delayErr =
  245. QObject::tr("Invalid delay, it must be a number greater than 0");
  246. const QString numberErr =
  247. QObject::tr("Invalid screen number, it must be non negative");
  248. const QString regionErr = QObject::tr(
  249. "Invalid region, use 'WxH+X+Y' or 'all' or 'screen0/screen1/...'.");
  250. auto numericChecker = [](const QString& delayValue) -> bool {
  251. bool ok;
  252. int value = delayValue.toInt(&ok);
  253. return ok && value >= 0;
  254. };
  255. auto regionChecker = [](const QString& region) -> bool {
  256. Region valueHandler;
  257. return valueHandler.check(region);
  258. };
  259. const QString pathErr =
  260. QObject::tr("Invalid path, must be an existing directory or a new file "
  261. "in an existing directory");
  262. auto pathChecker = [pathErr](const QString& pathValue) -> bool {
  263. QFileInfo fileInfo(pathValue);
  264. if (fileInfo.isDir() || fileInfo.dir().exists()) {
  265. return true;
  266. } else {
  267. AbstractLogger::error() << QObject::tr(pathErr.toLatin1().data());
  268. return false;
  269. }
  270. };
  271. const QString booleanErr =
  272. QObject::tr("Invalid value, it must be defined as 'true' or 'false'");
  273. auto booleanChecker = [](const QString& value) -> bool {
  274. return value == QLatin1String("true") ||
  275. value == QLatin1String("false");
  276. };
  277. contrastColorOption.addChecker(colorChecker, colorErr);
  278. mainColorOption.addChecker(colorChecker, colorErr);
  279. delayOption.addChecker(numericChecker, delayErr);
  280. regionOption.addChecker(regionChecker, regionErr);
  281. useLastRegionOption.addChecker(booleanChecker, booleanErr);
  282. pathOption.addChecker(pathChecker, pathErr);
  283. trayOption.addChecker(booleanChecker, booleanErr);
  284. autostartOption.addChecker(booleanChecker, booleanErr);
  285. showHelpOption.addChecker(booleanChecker, booleanErr);
  286. screenNumberOption.addChecker(numericChecker, numberErr);
  287. // Relationships
  288. parser.AddArgument(guiArgument);
  289. parser.AddArgument(screenArgument);
  290. parser.AddArgument(fullArgument);
  291. parser.AddArgument(launcherArgument);
  292. parser.AddArgument(configArgument);
  293. auto helpOption = parser.addHelpOption();
  294. auto versionOption = parser.addVersionOption();
  295. parser.AddOptions({ pathOption,
  296. clipboardOption,
  297. delayOption,
  298. regionOption,
  299. useLastRegionOption,
  300. rawImageOption,
  301. selectionOption,
  302. uploadOption,
  303. pinOption,
  304. acceptOnSelectOption },
  305. guiArgument);
  306. parser.AddOptions({ screenNumberOption,
  307. clipboardOption,
  308. pathOption,
  309. delayOption,
  310. regionOption,
  311. rawImageOption,
  312. uploadOption,
  313. pinOption },
  314. screenArgument);
  315. parser.AddOptions({ pathOption,
  316. clipboardOption,
  317. delayOption,
  318. regionOption,
  319. rawImageOption,
  320. uploadOption },
  321. fullArgument);
  322. parser.AddOptions({ autostartOption,
  323. filenameOption,
  324. trayOption,
  325. showHelpOption,
  326. mainColorOption,
  327. contrastColorOption,
  328. checkOption },
  329. configArgument);
  330. // Parse
  331. if (!parser.parse(qApp->arguments())) {
  332. goto finish;
  333. }
  334. // PROCESS DATA
  335. //--------------
  336. Flameshot::setOrigin(Flameshot::CLI);
  337. if (parser.isSet(helpOption) || parser.isSet(versionOption)) {
  338. } else if (parser.isSet(launcherArgument)) { // LAUNCHER
  339. reinitializeAsQApplication(argc, argv);
  340. Flameshot* flameshot = Flameshot::instance();
  341. flameshot->launcher();
  342. qApp->exec();
  343. } else if (parser.isSet(guiArgument)) { // GUI
  344. reinitializeAsQApplication(argc, argv);
  345. // Prevent multiple instances of 'flameshot gui' from running if not
  346. // configured to do so.
  347. if (!ConfigHandler().allowMultipleGuiInstances()) {
  348. auto* mutex = guiMutexLock();
  349. if (!mutex) {
  350. return 1;
  351. }
  352. QObject::connect(
  353. qApp, &QCoreApplication::aboutToQuit, qApp, [mutex]() {
  354. mutex->detach();
  355. delete mutex;
  356. });
  357. }
  358. // Option values
  359. QString path = parser.value(pathOption);
  360. if (!path.isEmpty()) {
  361. path = QDir(path).absolutePath();
  362. }
  363. int delay = parser.value(delayOption).toInt();
  364. QString region = parser.value(regionOption);
  365. bool useLastRegion = parser.isSet(useLastRegionOption);
  366. bool clipboard = parser.isSet(clipboardOption);
  367. bool raw = parser.isSet(rawImageOption);
  368. bool printGeometry = parser.isSet(selectionOption);
  369. bool pin = parser.isSet(pinOption);
  370. bool upload = parser.isSet(uploadOption);
  371. bool acceptOnSelect = parser.isSet(acceptOnSelectOption);
  372. CaptureRequest req(CaptureRequest::GRAPHICAL_MODE, delay, path);
  373. if (!region.isEmpty()) {
  374. auto selectionRegion = Region().value(region).toRect();
  375. req.setInitialSelection(selectionRegion);
  376. } else if (useLastRegion) {
  377. req.setInitialSelection(getLastRegion());
  378. }
  379. if (clipboard) {
  380. req.addTask(CaptureRequest::COPY);
  381. }
  382. if (raw) {
  383. req.addTask(CaptureRequest::PRINT_RAW);
  384. }
  385. if (!path.isEmpty()) {
  386. req.addSaveTask(path);
  387. }
  388. if (printGeometry) {
  389. req.addTask(CaptureRequest::PRINT_GEOMETRY);
  390. }
  391. if (pin) {
  392. req.addTask(CaptureRequest::PIN);
  393. }
  394. if (upload) {
  395. req.addTask(CaptureRequest::UPLOAD);
  396. }
  397. if (acceptOnSelect) {
  398. req.addTask(CaptureRequest::ACCEPT_ON_SELECT);
  399. if (!clipboard && !raw && path.isEmpty() && !printGeometry &&
  400. !pin && !upload) {
  401. req.addSaveTask();
  402. }
  403. }
  404. requestCaptureAndWait(req);
  405. } else if (parser.isSet(fullArgument)) { // FULL
  406. reinitializeAsQApplication(argc, argv);
  407. // Option values
  408. QString path = parser.value(pathOption);
  409. if (!path.isEmpty()) {
  410. path = QDir(path).absolutePath();
  411. }
  412. int delay = parser.value(delayOption).toInt();
  413. QString region = parser.value(regionOption);
  414. bool clipboard = parser.isSet(clipboardOption);
  415. bool raw = parser.isSet(rawImageOption);
  416. bool upload = parser.isSet(uploadOption);
  417. // Not a valid command
  418. CaptureRequest req(CaptureRequest::FULLSCREEN_MODE, delay);
  419. if (!region.isEmpty()) {
  420. req.setInitialSelection(Region().value(region).toRect());
  421. }
  422. if (clipboard) {
  423. req.addTask(CaptureRequest::COPY);
  424. }
  425. if (!path.isEmpty()) {
  426. req.addSaveTask(path);
  427. }
  428. if (raw) {
  429. req.addTask(CaptureRequest::PRINT_RAW);
  430. }
  431. if (upload) {
  432. req.addTask(CaptureRequest::UPLOAD);
  433. }
  434. if (!clipboard && path.isEmpty() && !raw && !upload) {
  435. req.addSaveTask();
  436. }
  437. requestCaptureAndWait(req);
  438. } else if (parser.isSet(screenArgument)) { // SCREEN
  439. reinitializeAsQApplication(argc, argv);
  440. QString numberStr = parser.value(screenNumberOption);
  441. // Option values
  442. int screenNumber =
  443. numberStr.startsWith(QLatin1String("-")) ? -1 : numberStr.toInt();
  444. QString path = parser.value(pathOption);
  445. if (!path.isEmpty()) {
  446. path = QDir(path).absolutePath();
  447. }
  448. int delay = parser.value(delayOption).toInt();
  449. QString region = parser.value(regionOption);
  450. bool clipboard = parser.isSet(clipboardOption);
  451. bool raw = parser.isSet(rawImageOption);
  452. bool pin = parser.isSet(pinOption);
  453. bool upload = parser.isSet(uploadOption);
  454. CaptureRequest req(CaptureRequest::SCREEN_MODE, delay, screenNumber);
  455. if (!region.isEmpty()) {
  456. if (region.startsWith("screen")) {
  457. AbstractLogger::error()
  458. << "The 'screen' command does not support "
  459. "'--region screen<N>'.\n"
  460. "See flameshot --help.\n";
  461. exit(1);
  462. }
  463. req.setInitialSelection(Region().value(region).toRect());
  464. }
  465. if (clipboard) {
  466. req.addTask(CaptureRequest::COPY);
  467. }
  468. if (raw) {
  469. req.addTask(CaptureRequest::PRINT_RAW);
  470. }
  471. if (!path.isEmpty()) {
  472. req.addSaveTask(path);
  473. }
  474. if (pin) {
  475. req.addTask(CaptureRequest::PIN);
  476. }
  477. if (upload) {
  478. req.addTask(CaptureRequest::UPLOAD);
  479. }
  480. if (!clipboard && !raw && path.isEmpty() && !pin && !upload) {
  481. req.addSaveTask();
  482. }
  483. requestCaptureAndWait(req);
  484. } else if (parser.isSet(configArgument)) { // CONFIG
  485. bool autostart = parser.isSet(autostartOption);
  486. bool filename = parser.isSet(filenameOption);
  487. bool tray = parser.isSet(trayOption);
  488. bool mainColor = parser.isSet(mainColorOption);
  489. bool contrastColor = parser.isSet(contrastColorOption);
  490. bool check = parser.isSet(checkOption);
  491. bool someFlagSet =
  492. (filename || tray || mainColor || contrastColor || check);
  493. if (check) {
  494. AbstractLogger err = AbstractLogger::error(AbstractLogger::Stderr);
  495. bool ok = ConfigHandler().checkForErrors(&err);
  496. if (ok) {
  497. AbstractLogger::info()
  498. << QStringLiteral("No errors detected.\n");
  499. goto finish;
  500. } else {
  501. return 1;
  502. }
  503. }
  504. if (!someFlagSet) {
  505. // Open gui when no options are given
  506. reinitializeAsQApplication(argc, argv);
  507. QObject::connect(
  508. qApp, &QApplication::lastWindowClosed, qApp, &QApplication::quit);
  509. Flameshot::instance()->config();
  510. qApp->exec();
  511. } else {
  512. ConfigHandler config;
  513. if (autostart) {
  514. config.setStartupLaunch(parser.value(autostartOption) ==
  515. "true");
  516. }
  517. if (filename) {
  518. QString newFilename(parser.value(filenameOption));
  519. config.setFilenamePattern(newFilename);
  520. FileNameHandler fh;
  521. QTextStream(stdout)
  522. << QStringLiteral("The new pattern is '%1'\n"
  523. "Parsed pattern example: %2\n")
  524. .arg(newFilename)
  525. .arg(fh.parsedPattern());
  526. }
  527. if (tray) {
  528. config.setDisabledTrayIcon(parser.value(trayOption) == "false");
  529. }
  530. if (mainColor) {
  531. // TODO use value handler
  532. QString colorCode = parser.value(mainColorOption);
  533. QColor parsedColor(colorCode);
  534. config.setUiColor(parsedColor);
  535. }
  536. if (contrastColor) {
  537. QString colorCode = parser.value(contrastColorOption);
  538. QColor parsedColor(colorCode);
  539. config.setContrastUiColor(parsedColor);
  540. }
  541. }
  542. }
  543. finish:
  544. #endif
  545. return 0;
  546. }