[submodule "monero"]
path = monero
url = https://github.com/feather-wallet/monero.git
-[submodule "src/third-party/singleapplication"]
- path = src/third-party/singleapplication
- url = https://github.com/itay-grudev/SingleApplication.git
[submodule "src/third-party/polyseed"]
path = src/third-party/polyseed
url = https://github.com/tevador/polyseed.git
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2024 The Monero Project
+
+#include "Application.h"
+
+#include "config.h"
+
+#include <QLocalSocket>
+#include <QLockFile>
+#include <QStandardPaths>
+
+namespace
+{
+ constexpr int WaitTimeoutMSec = 150;
+ const char BlockSizeProperty[] = "blockSize";
+} // namespace
+
+Application::Application(int& argc, char** argv)
+ : QApplication(argc, argv)
+ , m_alreadyRunning(false)
+ , m_lockFile(nullptr)
+{
+ QString userName = qgetenv("USER");
+ if (userName.isEmpty()) {
+ userName = qgetenv("USERNAME");
+ }
+ QString identifier = "feather";
+ if (!userName.isEmpty()) {
+ identifier += "-" + userName;
+ }
+
+ QString lockName = identifier + ".lock";
+ m_socketName = identifier + ".socket";
+
+ // According to documentation we should use RuntimeLocation on *nixes, but even Qt doesn't respect
+ // this and creates sockets in TempLocation, so let's be consistent.
+ m_lockFile = new QLockFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + lockName);
+ m_lockFile->setStaleLockTime(0);
+ m_lockFile->tryLock();
+
+ m_lockServer.setSocketOptions(QLocalServer::UserAccessOption);
+ connect(&m_lockServer, SIGNAL(newConnection()), this, SIGNAL(anotherInstanceStarted()));
+ connect(&m_lockServer, &QLocalServer::newConnection, this, &Application::processIncomingConnection);
+
+ switch (m_lockFile->error()) {
+ case QLockFile::NoError:
+ // No existing lock was found, start listener
+ m_lockServer.listen(m_socketName);
+ break;
+ case QLockFile::LockFailedError: {
+ // Attempt to connect to the existing instance
+ QLocalSocket client;
+ for (int i = 0; i < 3; ++i) {
+ client.connectToServer(m_socketName);
+ if (client.waitForConnected(WaitTimeoutMSec)) {
+ // Connection succeeded, this will raise the existing window if minimized
+ client.abort();
+ m_alreadyRunning = true;
+ break;
+ }
+ }
+
+ if (!m_alreadyRunning) {
+ // If we get here then the original instance is likely dead
+ qWarning() << "Existing single-instance lock file is invalid. Launching new instance.";
+
+ // forceably reset the lock file
+ m_lockFile->removeStaleLockFile();
+ m_lockFile->tryLock();
+ // start the listen server
+ m_lockServer.listen(m_socketName);
+ }
+ break;
+ }
+ default:
+ qWarning() << "The lock file could not be created. Single-instance mode disabled.";
+ }
+}
+
+Application::~Application()
+{
+ if (m_lockFile) {
+ m_lockFile->unlock();
+ delete m_lockFile;
+ }
+}
+
+bool Application::isAlreadyRunning() const
+{
+ return m_alreadyRunning;
+}
+
+void Application::processIncomingConnection()
+{
+ qDebug() << "We got an incoming connection";
+ if (m_lockServer.hasPendingConnections()) {
+ QLocalSocket* socket = m_lockServer.nextPendingConnection();
+ socket->setProperty(BlockSizeProperty, 0);
+ }
+}
--- /dev/null
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2024 The Monero Project
+
+#ifndef FEATHER_APPLICATION_H
+#define FEATHER_APPLICATION_H
+
+#include <QApplication>
+#include <QtNetwork/qlocalserver.h>
+
+class QLockFile;
+class QSocketNotifier;
+
+class Application : public QApplication {
+ Q_OBJECT
+
+public:
+ Application(int& argc, char** argv);
+ ~Application() override;
+
+ bool isAlreadyRunning() const;
+
+signals:
+ void anotherInstanceStarted();
+
+private slots:
+ void processIncomingConnection();
+
+private:
+ bool m_alreadyRunning;
+ QLockFile* m_lockFile;
+ QLocalServer m_lockServer;
+ QString m_socketName;
+};
+
+
+#endif //FEATHER_APPLICATION_H
find_package(Qt6 REQUIRED COMPONENTS ${QT_COMPONENTS})
-if (NOT APPLE)
- set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
- add_subdirectory(third-party/singleapplication)
-endif()
-
if (CHECK_UPDATES)
add_subdirectory(openpgp)
endif()
${BCUR_LIBRARY}
)
-if(NOT APPLE)
- target_link_libraries(feather PRIVATE SingleApplication::SingleApplication)
-endif()
-
if(CHECK_UPDATES)
target_link_libraries(feather PRIVATE openpgp)
endif()
#include "WindowManager.h"
-#include <QApplication>
#include <QDialogButtonBox>
#include <QInputDialog>
#include <QMessageBox>
#include <QWindow>
+#include "Application.h"
#include "constants.h"
#include "dialog/PasswordDialog.h"
#include "dialog/SplashDialog.h"
connect(m_walletManager, &WalletManager::deviceError, this, &WindowManager::onDeviceError);
connect(m_walletManager, &WalletManager::walletPassphraseNeeded, this, &WindowManager::onWalletPassphraseNeeded);
+ connect(qApp, SIGNAL(anotherInstanceStarted()), this, SLOT(raise()));
connect(qApp, &QGuiApplication::lastWindowClosed, this, &WindowManager::quitAfterLastWindow);
m_tray = new QSystemTrayIcon(icons()->icon("appicons/64x64.png"));
qDebug() << "~WindowManager";
m_cleanupThread->quit();
m_cleanupThread->wait();
+ qDebug() << "Cleanup thread done";
}
// ######################## APPLICATION LIFECYCLE ########################
torManager()->stop();
+ qDebug() << "Calling QApplication::quit()";
QApplication::quit();
}
void closeWindow(MainWindow *window);
void showWizard(WalletWizard::Page startPage);
void restartApplication(const QString &binaryFilename);
- void raise();
void showSettings(Nodes *nodes, QWidget *parent, bool showProxyTab = false);
void tryOpenWallet(const QString &path, const QString &password);
private slots:
+ void raise();
void onWalletOpened(Wallet *wallet);
void onWalletCreated(Wallet *wallet);
void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path);
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2024 The Monero Project
-#include <QResource>
-#include <QApplication>
-#include <QtCore>
-#include <QtGui>
-#if !defined(Q_OS_MAC)
-#include <singleapplication.h>
-#endif
-
+#include "Application.h"
#include "config-feather.h"
#include "constants.h"
-#include "MainWindow.h"
#include "utils/EventFilter.h"
#include "utils/os/Prestium.h"
#include "WindowManager.h"
#include <fstream>
#endif
-#include <QObject>
-
#if defined(Q_OS_WIN)
#include <windows.h>
#include <vfw.h>
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round);
#endif
-#if defined(Q_OS_MAC)
- // https://github.com/itay-grudev/SingleApplication/issues/136#issuecomment-1925441403
- QApplication app(argc, argv);
-#else
- SingleApplication app(argc, argv);
-#endif
+ Application app(argc, argv);
QApplication::setApplicationName("Feather");
QApplication::setApplicationVersion(FEATHER_VERSION);
return EXIT_SUCCESS;
}
+ if (app.isAlreadyRunning()) {
+ qWarning() << "Another instance of Feather is already running";
+ return EXIT_SUCCESS;
+ }
+
bool stagenet = parser.isSet(stagenetOption);
bool testnet = parser.isSet(testnetOption);
bool quiet = parser.isSet(quietModeOption);
conf()->set(Config::restartRequired, false);
- parser.process(app); // Parse again for --help and --version
-
if (!quiet) {
QMap<QString, QString> info;
info["Qt"] = QT_VERSION_STR;
auto wm = windowManager();
wm->setEventFilter(&filter);
-#if !defined(Q_OS_MAC)
- QObject::connect(&app, &SingleApplication::instanceStarted, [&wm]() {
- wm->raise();
- });
-#endif
-
- int exitCode = QApplication::exec();
- qDebug() << "QApplication::exec() returned";
+ int exitCode = Application::exec();
+ qDebug() << "Application::exec() returned";
return exitCode;
}
+++ /dev/null
-Subproject commit 3e8e85d1a487e433751711a8a090659684d42e3b