Skip to content

Commit

Permalink
Handle QEvent::FileOpen events on macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
equeim committed Dec 18, 2023
1 parent b024880 commit 1570ee1
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 10 deletions.
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ if (UNIX)
PRIVATE
macoshelpers.mm
filemanagerlauncher_macos.mm
ipc/fileopeneventhandler.h
ipc/fileopeneventhandler.cpp
)
else ()
target_sources(
Expand Down Expand Up @@ -389,7 +391,7 @@ if (BUILD_TESTING)
message(STATUS "Found cpp-httplib ${httplib_VERSION} using pkg-config")
if (httplib_VERSION VERSION_GREATER_EQUAL httplib_next_unsupported_version)
message(WARNING "cpp-httplib version ${httplib_VERSION} is not supported. Compilation or tests may fail")
endif()
endif ()
else ()
message(STATUS "Did not found cpp-httplib using pkg-config")
endif ()
Expand Down
57 changes: 57 additions & 0 deletions src/ipc/fileopeneventhandler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2015-2023 Alexey Rochev
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "fileopeneventhandler.h"

#include <QCoreApplication>
#include <QFileOpenEvent>

#include "log/log.h"

namespace tremotesf {
FileOpenEventHandler::FileOpenEventHandler(QObject* parent) : QObject(parent) {
QCoreApplication::instance()->installEventFilter(this);
}

FileOpenEventHandler::~FileOpenEventHandler() { QCoreApplication::instance()->removeEventFilter(this); }

bool FileOpenEventHandler::eventFilter(QObject* watched, QEvent* event) {
// In case of multiple files / urls, Qt send separate QFileOpenEvent per file in a loop
// Call processPendingEvents() in the next event loop iteration to process all events at once
if (event->type() == QEvent::FileOpen) {
auto* const e = static_cast<QFileOpenEvent*>(event);
if (!e->file().isEmpty() || e->url().isValid()) {
logInfo("Received QEvent::FileOpen");
auto pendingEvent = [&] {
if (!e->file().isEmpty()) {
logInfo("file = {}", e->file());
return PendingEvent{.fileOrUrl = e->file(), .isFile = true};
}
auto url = e->url().toString();
logInfo("url = {}", url);
return PendingEvent{.fileOrUrl = std::move(url), .isFile = false};
}();
if (mPendingEvents.empty()) {
QMetaObject::invokeMethod(this, &FileOpenEventHandler::processPendingEvents, Qt::QueuedConnection);
}
mPendingEvents.push_back(std::move(pendingEvent));
}
}
return QObject::eventFilter(watched, event);
}

void FileOpenEventHandler::processPendingEvents() {
QStringList files{};
QStringList urls{};
for (auto& event : mPendingEvents) {
if (event.isFile) {
files.push_back(std::move(event.fileOrUrl));
} else {
urls.push_back(std::move(event.fileOrUrl));
}
}
mPendingEvents.clear();
emit filesOpeningRequested(files, urls);
}
}
36 changes: 36 additions & 0 deletions src/ipc/fileopeneventhandler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2015-2023 Alexey Rochev
//
// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef TREMOTESF_FILEOPENEVENTHANDLER_H
#define TREMOTESF_FILEOPENEVENTHANDLER_H

#include <vector>
#include <QObject>

class QFileOpenEvent;

namespace tremotesf {
class FileOpenEventHandler : public QObject {
Q_OBJECT
public:
explicit FileOpenEventHandler(QObject* parent = nullptr);
~FileOpenEventHandler() override;
Q_DISABLE_COPY_MOVE(FileOpenEventHandler)
bool eventFilter(QObject* watched, QEvent* event) override;

private:
void processPendingEvents();

struct PendingEvent {
QString fileOrUrl{};
bool isFile{};
};
std::vector<PendingEvent> mPendingEvents{};

signals:
void filesOpeningRequested(const QStringList& files, const QStringList& urls);
};
}

#endif // TREMOTESF_FILEOPENEVENTHANDLER_H
76 changes: 68 additions & 8 deletions src/startup/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include <chrono>
#include <utility>

#include <QApplication>
#include <QIcon>
#include <QLibraryInfo>
#include <QLoggingCategory>
#include <QLocale>
#include <QTimer>
#include <QTranslator>

#include <fmt/ranges.h>

#include "commandlineparser.h"
#include "fileutils.h"
#include "literals.h"
Expand All @@ -21,10 +27,71 @@
#include "ui/savewindowstatedispatcher.h"
#include "ui/screens/mainwindow/mainwindow.h"

#ifdef Q_OS_MACOS
# include "ipc/fileopeneventhandler.h"
#endif

SPECIALIZE_FORMATTER_FOR_QDEBUG(QLocale)

using namespace std::chrono_literals;
using namespace tremotesf;

namespace {
#ifdef Q_OS_MACOS
std::pair<QStringList, QStringList> receiveFileOpenEvents(int& argc, char** argv) {
std::pair<QStringList, QStringList> filesAndUrls{};
logInfo("Waiting for file open events");
const QGuiApplication app(argc, argv);
const FileOpenEventHandler handler{};
QObject::connect(
&handler,
&FileOpenEventHandler::filesOpeningRequested,
&app,
[&](const auto& files, const auto& urls) {
filesAndUrls = {files, urls};
QCoreApplication::quit();
}
);
QTimer::singleShot(500ms, &app, [] {
logInfo("Did not receive file open events");
QCoreApplication::quit();
});
QCoreApplication::exec();
return filesAndUrls;
}
#endif

bool shouldExitBecauseAnotherInstanceIsRunning(
[[maybe_unused]] int& argc, [[maybe_unused]] char** argv, const CommandLineArgs& args
) {
const auto client = IpcClient::createInstance();
if (!client->isConnected()) {
return false;
}
logInfo("Only one instance of Tremotesf can be run at the same time");
const auto activateOtherInstance = [&client](const QStringList& files, const QStringList& urls) {
if (files.isEmpty() && urls.isEmpty()) {
logInfo("Activating other instance");
client->activateWindow();
} else {
logInfo("Activating other instance and requesting torrent adding");
logInfo("files = {}", files);
logInfo("urls = {}", urls);
client->addTorrents(files, urls);
}
};
#ifdef Q_OS_MACOS
if (args.files.isEmpty() && args.urls.isEmpty()) {
const auto [files, urls] = receiveFileOpenEvents(argc, argv);
activateOtherInstance(files, urls);
return true;
}
#endif
activateOtherInstance(args.files, args.urls);
return true;
}
}

int main(int argc, char** argv) {
// This does not need QApplication instance, and we need it in windowsInitPrelude()
QCoreApplication::setOrganizationName(TREMOTESF_EXECUTABLE_NAME ""_l1);
Expand Down Expand Up @@ -63,14 +130,7 @@ int main(int argc, char** argv) {
// Setup handler for UNIX signals or Windows console handler
const SignalHandler signalHandler{};

// Send command to another instance
if (const auto client = IpcClient::createInstance(); client->isConnected()) {
logInfo("Only one instance of Tremotesf can be run at the same time");
if (args.files.isEmpty() && args.urls.isEmpty()) {
client->activateWindow();
} else {
client->addTorrents(args.files, args.urls);
}
if (shouldExitBecauseAnotherInstanceIsRunning(argc, argv, args)) {
return EXIT_SUCCESS;
}

Expand Down
16 changes: 15 additions & 1 deletion src/ui/screens/mainwindow/mainwindowviewmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
#include "ui/notificationscontroller.h"
#include "settings.h"

#ifdef Q_OS_MACOS
# include "ipc/fileopeneventhandler.h"
#endif

SPECIALIZE_FORMATTER_FOR_QDEBUG(QUrl)

SPECIALIZE_FORMATTER_FOR_Q_ENUM(Qt::DropAction)
Expand Down Expand Up @@ -65,7 +69,7 @@ namespace tremotesf {
);
}

auto ipcServer = IpcServer::createInstance(this);
const auto* const ipcServer = IpcServer::createInstance(this);
QObject::connect(
ipcServer,
&IpcServer::windowActivationRequested,
Expand All @@ -82,6 +86,16 @@ namespace tremotesf {
[=, this](const auto& files, const auto& urls) { addTorrents(files, urls); }
);

#ifdef Q_OS_MACOS
const auto* const handler = new FileOpenEventHandler(this);
QObject::connect(
handler,
&FileOpenEventHandler::filesOpeningRequested,
this,
[=, this](const auto& files, const auto& urls) { addTorrents(files, urls); }
);
#endif

QObject::connect(&mRpc, &Rpc::connectedChanged, this, [this] {
if (mRpc.isConnected()) {
if (delayedTorrentAddMessageTimer) {
Expand Down

0 comments on commit 1570ee1

Please sign in to comment.