Files
deskflow/src/apps/deskflow-gui/deskflow-gui.cpp
sithlord48 d3c0ce8895 refactor: move all exit codes to new ExitCodes
add new exitcode s_exitDuplicate for when exiting because of already running instance
2025-08-21 12:34:50 +01:00

179 lines
5.5 KiB
C++

/*
* Deskflow -- mouse and keyboard sharing utility
* SPDX-FileCopyrightText: (C) 2024 Chris Rizzitello <sithord48@gmail.com>
* SPDX-FileCopyrightText: (C) 2012 - 2024 Symless Ltd.
* SPDX-FileCopyrightText: (C) 2008 Volker Lanz <vl@fidra.de>
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "VersionInfo.h"
#include "common/Constants.h"
#include "common/ExitCodes.h"
#include "common/UrlConstants.h"
#include "gui/Diagnostic.h"
#include "gui/DotEnv.h"
#include "gui/Logger.h"
#include "gui/MainWindow.h"
#include "gui/Messages.h"
#include "gui/StyleUtils.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QLocalSocket>
#include <QMessageBox>
#include <QSharedMemory>
#if defined(Q_OS_MAC)
#include <Carbon/Carbon.h>
#include <cstdlib>
#endif
#if defined(Q_OS_UNIX) && defined(QT_DEBUG)
#include <QLoggingCategory>
#endif
using namespace deskflow::gui;
#if defined(Q_OS_MAC)
bool checkMacAssistiveDevices();
#endif
const static auto kHeader = QStringLiteral("%1: %2\n").arg(kAppName, kDisplayVersion);
int main(int argc, char *argv[])
{
#if defined(Q_OS_UNIX) && defined(QT_DEBUG)
// Fixes Fedora bug where qDebug() messages aren't printed.
QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true\nqt.*=false"));
#endif
QCoreApplication::setApplicationName(kAppName);
QCoreApplication::setOrganizationName(kAppName);
QCoreApplication::setApplicationVersion(kVersion);
QCoreApplication::setOrganizationDomain(kOrgDomain); // used in prefix, can't be a url
QGuiApplication::setDesktopFileName(QStringLiteral("org.deskflow.deskflow"));
QApplication app(argc, argv);
// Add Command Line Options
auto helpOption = QCommandLineOption({"h", "help"}, "Display Help on the command line");
auto versionOption = QCommandLineOption({"v", "version"}, "Display version information");
auto resetOption = QCommandLineOption("reset", "Reset all settings");
QCommandLineParser parser;
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
parser.addOption(helpOption);
parser.addOption(versionOption);
parser.addOption(resetOption);
parser.parse(QCoreApplication::arguments());
if (!parser.errorText().isEmpty()) {
qCritical().noquote() << parser.errorText() << "\nUse --help for more information.";
return s_exitArgs;
}
if (parser.isSet(helpOption)) {
QTextStream(stdout) << kHeader << QStringLiteral(" %1\n\n").arg(kAppDescription)
<< parser.helpText().replace(QApplication::applicationFilePath(), kAppId);
return s_exitSuccess;
}
if (parser.isSet(versionOption)) {
QTextStream(stdout) << kHeader << kCopyright << Qt::endl;
return s_exitSuccess;
}
// Create a shared memory segment with a unique key
// This is to prevent a new instance from running if one is already running
QSharedMemory sharedMemory("deskflow-gui");
// Attempt to attach first and detach in order to clean up stale shm chunks
// This can happen if the previous instance was killed or crashed
if (sharedMemory.attach())
sharedMemory.detach();
// If we can create 1 byte of SHM we are the only instance
if (!sharedMemory.create(1)) {
// Ping the running instance to have it show itself
QLocalSocket socket;
socket.connectToServer("deskflow-gui", QLocalSocket::ReadOnly);
if (!socket.waitForConnected()) {
// If we can't connect to the other instance tell the user its running.
// This should never happen but just incase we should show something
QMessageBox::information(nullptr, QObject::tr("Deskflow"), QObject::tr("Deskflow is already running"));
}
socket.disconnectFromServer();
return s_exitDuplicate;
}
#if !defined(Q_OS_MAC)
// causes dark mode to be used on some DE's
if (qEnvironmentVariable("XDG_CURRENT_DESKTOP") != QLatin1String("KDE")) {
QApplication::setStyle("fusion");
}
#endif
// Sets the fallback icon path and fallback theme
const auto themeName = QStringLiteral("deskflow-%1").arg(iconMode());
if (QIcon::themeName().isEmpty())
QIcon::setThemeName(themeName);
else
QIcon::setFallbackThemeName(themeName);
QIcon::setFallbackSearchPaths({QStringLiteral(":/icons/%1").arg(themeName)});
qInstallMessageHandler(deskflow::gui::messages::messageHandler);
qInfo("%s v%s", kAppName, kDisplayVersion);
dotenv();
Logger::instance().loadEnvVars();
#if defined(Q_OS_MAC)
if (app.applicationDirPath().startsWith("/Volumes/")) {
QString msgBody = QStringLiteral(
"Please drag %1 to the Applications folder, "
"and open it from there."
);
QMessageBox::information(nullptr, kAppName, msgBody.arg(kAppName));
return 1;
}
if (!checkMacAssistiveDevices()) {
return 1;
}
#endif
// --no-reset
if (parser.isSet(resetOption)) {
diagnostic::clearSettings(false);
}
MainWindow mainWindow;
mainWindow.open();
return QApplication::exec();
}
#if defined(Q_OS_MAC)
bool checkMacAssistiveDevices()
{
// new in mavericks, applications are trusted individually
// with use of the accessibility api. this call will show a
// prompt which can show the security/privacy/accessibility
// tab, with a list of allowed applications. deskflow should
// show up there automatically, but will be unchecked.
if (AXIsProcessTrusted()) {
return true;
}
const void *keys[] = {kAXTrustedCheckOptionPrompt};
const void *trueValue[] = {kCFBooleanTrue};
CFDictionaryRef options = CFDictionaryCreate(nullptr, keys, trueValue, 1, nullptr, nullptr);
bool result = AXIsProcessTrustedWithOptions(options);
CFRelease(options);
return result;
}
#endif