Cockatrice/cockatrice/src/single_instance_manager.cpp
BruebachL 59d90db3c7
Define Cockatrice as an editor/handler for .cod files and cockatrice:// protocol on all platforms (#6775)
* [Application] Add single instance guard and mime types.

Took 2 hours 39 minutes

Took 18 minutes

Took 5 minutes

Took 12 seconds


Took 11 seconds

* Rework

Took 30 minutes


Took 50 seconds

* Only enforce single instance if launched with arguments.

Took 5 minutes

* Prototype intents

Took 53 minutes

Took 6 seconds

* Connect/disconnect and join game/room intents.

Took 3 hours 14 minutes

Took 2 seconds

Took 15 seconds

* Fix include.

Took 1 minute


Took 23 seconds

Took 2 seconds

* Mac handling.

Took 10 minutes

Took 12 seconds

Took 3 minutes

* Lint.

Took 3 minutes

* Rebase.

Took 3 minutes

Took 17 seconds

* Implement UrlSchemeEventFilter

Took 10 minutes

Took 7 seconds

* Qt Moc

Took 3 minutes

* Modern PList.

Took 21 minutes

Took 1 minute

* Debug output.

Took 6 minutes

Took 19 minutes

* Watch file:// prefix.

Took 15 minutes

Took 7 seconds

* Better handler.

Took 6 minutes

* Don't store reference in member

Took 5 minutes

* Move impl to cpp, fix lifetime issues.

Took 11 minutes

Took 2 minutes

* Better single-instance handoff, url intent harded

copy game link context-menu
Polish for installers

Took 35 minutes

Took 8 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-08-08 19:02:19 +02:00

126 lines
3.8 KiB
C++

#include "single_instance_manager.h"
#include <QDir>
SingleInstanceManager::SingleInstanceManager(QObject *parent) : QObject(parent)
{
}
bool SingleInstanceManager::tryRun(const QStringList &filesToSend)
{
// Scope the socket name to the current user. On Linux the default abstract
// namespace is system-wide, so a plain name would let one user's instance
// hijack another user's session.
QString userName = qEnvironmentVariable("USER");
if (userName.isEmpty()) {
userName = qEnvironmentVariable("USERNAME");
}
if (userName.isEmpty()) {
userName = QDir::home().dirName();
}
serverName = QStringLiteral("CockatriceSingleInstance-%1").arg(userName);
// Hand off to an already-running primary instance if one exists.
if (forwardToPrimary(filesToSend)) {
return false;
}
// No primary instance is currently reachable, so become the primary.
server = new QLocalServer(this);
connect(server, &QLocalServer::newConnection, this, &SingleInstanceManager::handleNewConnection);
if (server->listen(serverName)) {
return true;
}
// Another instance may have started while we were probing; hand off to it
// instead of stealing its socket.
if (forwardToPrimary(filesToSend)) {
return false;
}
// The socket is stale (left over by a crashed instance): remove it and
// retry. If that still fails, another instance just took the name.
QLocalServer::removeServer(serverName);
if (server->listen(serverName)) {
return true;
}
forwardToPrimary(filesToSend);
return false;
}
bool SingleInstanceManager::forwardToPrimary(const QStringList &filesToSend)
{
QLocalSocket socket;
socket.connectToServer(serverName);
if (!socket.waitForConnected(200)) {
return false;
}
// Serialize payload with length prefix
QByteArray payload;
QDataStream out(&payload, QIODevice::WriteOnly);
out << filesToSend;
QByteArray message;
QDataStream msgStream(&message, QIODevice::WriteOnly);
msgStream << quint32(payload.size());
message.append(payload);
socket.write(message);
socket.flush();
socket.waitForBytesWritten(1000);
return true;
}
void SingleInstanceManager::handleNewConnection()
{
QLocalSocket *socket = server->nextPendingConnection();
// Per-connection state. QSharedPointer keeps the buffers alive for as long
// as the connection handler is attached to the socket.
auto buffer = QSharedPointer<QByteArray>::create();
auto expectedSize = QSharedPointer<quint32>::create(0);
connect(socket, &QLocalSocket::readyRead, this, [this, socket, buffer, expectedSize]() {
buffer->append(socket->readAll());
QDataStream stream(buffer.data(), QIODevice::ReadOnly);
while (true) {
// Step 1: read size
if (*expectedSize == 0) {
if (buffer->size() < static_cast<int>(sizeof(quint32))) {
return;
}
stream >> *expectedSize;
}
// Step 2: wait for full payload
if (buffer->size() < static_cast<int>(sizeof(quint32) + *expectedSize)) {
return;
}
// Step 3: extract payload
QByteArray payload = buffer->mid(sizeof(quint32), *expectedSize);
QDataStream payloadStream(&payload, QIODevice::ReadOnly);
QStringList files;
payloadStream >> files;
emit filesReceived(files);
// Reset buffer (single message use-case)
buffer->clear();
*expectedSize = 0;
socket->disconnectFromServer();
return;
}
});
connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater);
}