mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
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>
This commit is contained in:
parent
27fb5e51de
commit
59d90db3c7
43 changed files with 1372 additions and 13 deletions
126
cockatrice/src/single_instance_manager.cpp
Normal file
126
cockatrice/src/single_instance_manager.cpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue