mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Reworked IPC class:
* Simpler design * Suport for named events * Support for checking if events were handled * Support for sending events to specific application instance
This commit is contained in:
parent
244d6daca8
commit
9523484bfe
379
src/ipc.cpp
379
src/ipc.cpp
|
@ -16,37 +16,39 @@
|
||||||
|
|
||||||
|
|
||||||
#include "src/ipc.h"
|
#include "src/ipc.h"
|
||||||
|
#include "src/misc/settings.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
IPC::IPC() :
|
|
||||||
globalMemory{"qtox"}
|
IPC::IPC()
|
||||||
|
: globalMemory{"qtox-" IPC_PROTOCOL_VERSION}
|
||||||
{
|
{
|
||||||
qRegisterMetaType<IPCEventHandler>("IPCEventHandler");
|
qRegisterMetaType<IPCEventHandler>("IPCEventHandler");
|
||||||
|
|
||||||
ownerTimer.setInterval(EVENT_TIMER_MS);
|
timer.setInterval(EVENT_TIMER_MS);
|
||||||
ownerTimer.setSingleShot(true);
|
timer.setSingleShot(true);
|
||||||
connect(&ownerTimer, &QTimer::timeout, this, &IPC::processEvents);
|
connect(&timer, &QTimer::timeout, this, &IPC::processEvents);
|
||||||
|
|
||||||
// The first started instance gets to manage the shared memory by taking ownership
|
// The first started instance gets to manage the shared memory by taking ownership
|
||||||
// Every time it processes events it updates the global shared timestamp
|
// Every time it processes events it updates the global shared timestamp "lastProcessed"
|
||||||
// If the timestamp isn't updated, that's a timeout and someone else can take ownership
|
// If the timestamp isn't updated, that's a timeout and someone else can take ownership
|
||||||
// This is a safety measure, in case one of the clients crashes
|
// This is a safety measure, in case one of the clients crashes
|
||||||
// If the owner exits normally, it can set the timestamp to 0 first to immediately give ownership
|
// If the owner exits normally, it can set the timestamp to 0 first to immediately give ownership
|
||||||
// We keep one shared page, starting with the 64bit ID of the current owner and last timestamp
|
|
||||||
// then various events to be processed by the owner with a 16bit size then data each
|
|
||||||
// Each event is in its own chunk of data, the last chunk is followed by a chunk of size 0
|
|
||||||
|
|
||||||
qsrand(time(0));
|
qsrand(time(0));
|
||||||
globalId = ((uint64_t)qrand()) * ((uint64_t)qrand()) * ((uint64_t)qrand());
|
globalId = ((uint64_t)qrand()) * ((uint64_t)qrand()) * ((uint64_t)qrand());
|
||||||
qDebug() << "IPC: Our global ID is "<<globalId;
|
qDebug() << "IPC: Our global ID is " << globalId;
|
||||||
if (globalMemory.create(MEMORY_SIZE))
|
if (globalMemory.create(sizeof(IPCMemory)))
|
||||||
{
|
{
|
||||||
qDebug() << "IPC: Creating the global shared memory and taking ownership";
|
qDebug() << "IPC: Creating the global shared memory and taking ownership";
|
||||||
if (globalMemory.lock())
|
if (globalMemory.lock())
|
||||||
{
|
{
|
||||||
*(uint64_t*)globalMemory.data() = globalId;
|
IPCMemory* mem = global();
|
||||||
updateGlobalTimestamp();
|
memset(mem, 0, sizeof(IPCMemory));
|
||||||
|
mem->globalId = globalId;
|
||||||
|
mem->lastProcessed = time(0);
|
||||||
globalMemory.unlock();
|
globalMemory.unlock();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -64,18 +66,59 @@ IPC::IPC() :
|
||||||
return; // We won't be able to do any IPC without being attached, let's get outta here
|
return; // We won't be able to do any IPC without being attached, let's get outta here
|
||||||
}
|
}
|
||||||
|
|
||||||
ownerTimer.start();
|
timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
IPC::~IPC()
|
IPC::~IPC()
|
||||||
{
|
{
|
||||||
|
if (isCurrentOwner())
|
||||||
|
{
|
||||||
|
if (globalMemory.lock())
|
||||||
|
{
|
||||||
|
global()->globalId = 0;
|
||||||
|
globalMemory.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t IPC::postEvent(const QString &name, const QByteArray& data/*=QByteArray()*/, uint32_t dest/*=0*/)
|
||||||
|
{
|
||||||
|
QByteArray binName = name.toUtf8();
|
||||||
|
if (binName.length() > (int32_t)sizeof(IPCEvent::name))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (data.length() > (int32_t)sizeof(IPCEvent::data))
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (globalMemory.lock())
|
if (globalMemory.lock())
|
||||||
{
|
{
|
||||||
char* data = (char*)globalMemory.data();
|
IPCEvent* evt = 0;
|
||||||
if (data)
|
IPCMemory* mem = global();
|
||||||
*(time_t*)(data+sizeof(globalId)) = 0;
|
time_t result = 0;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; !evt && i < EVENT_QUEUE_SIZE; i++)
|
||||||
|
{
|
||||||
|
if (mem->events[i].posted == 0)
|
||||||
|
evt = &mem->events[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evt)
|
||||||
|
{
|
||||||
|
memset(evt, 0, sizeof(IPCEvent));
|
||||||
|
memcpy(evt->name, binName.constData(), binName.length());
|
||||||
|
memcpy(evt->data, data.constData(), data.length());
|
||||||
|
mem->lastEvent = evt->posted = result = qMax(mem->lastEvent + 1, time(0));
|
||||||
|
evt->dest = dest;
|
||||||
|
evt->sender = getpid();
|
||||||
|
qDebug() << "IPC: postEvent " << name << "to" << dest;
|
||||||
|
}
|
||||||
globalMemory.unlock();
|
globalMemory.unlock();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
qDebug() << "IPC: Failed to lock in postEvent()";
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IPC::isCurrentOwner()
|
bool IPC::isCurrentOwner()
|
||||||
|
@ -88,190 +131,168 @@ bool IPC::isCurrentOwner()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qWarning() << "IPC:isCurrentOwner failed to lock, returning false";
|
qWarning() << "IPC: isCurrentOwner failed to lock, returning false";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPC::registerEventHandler(IPCEventHandler handler)
|
void IPC::registerEventHandler(const QString &name, IPCEventHandler handler)
|
||||||
{
|
{
|
||||||
eventHandlers += handler;
|
eventHandlers[name] = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPC::isEventProcessed(time_t time)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (globalMemory.lock())
|
||||||
|
{
|
||||||
|
if (difftime(global()->lastProcessed, time) > 0)
|
||||||
|
{
|
||||||
|
IPCMemory* mem = global();
|
||||||
|
for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; i++)
|
||||||
|
{
|
||||||
|
if (mem->events[i].posted == time && mem->events[i].processed)
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
globalMemory.unlock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "IPC: isEventProcessed failed to lock, returning false";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPC::isEventAccepted(time_t time)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (globalMemory.lock())
|
||||||
|
{
|
||||||
|
// if (difftime(global()->lastProcessed, time) > 0)
|
||||||
|
{
|
||||||
|
IPCMemory* mem = global();
|
||||||
|
for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; i++)
|
||||||
|
{
|
||||||
|
if (mem->events[i].posted == time)
|
||||||
|
{
|
||||||
|
result = mem->events[i].accepted;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
globalMemory.unlock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "IPC: isEventAccepted failed to lock, returning false";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPC::waitUntilProcessed(time_t postTime, int32_t timeout/*=-1*/)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
time_t start = time(0);
|
||||||
|
while (!(result = isEventProcessed(postTime)))
|
||||||
|
{
|
||||||
|
qApp->processEvents();
|
||||||
|
if (timeout > 0 && difftime(time(0), start) >= timeout)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::IPCEvent *IPC::fetchEvent()
|
||||||
|
{
|
||||||
|
IPCMemory* mem = global();
|
||||||
|
for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; i++)
|
||||||
|
{
|
||||||
|
IPCEvent* evt = &mem->events[i];
|
||||||
|
|
||||||
|
// Garbage-collect events that were not processed in EVENT_GC_TIMEOUT
|
||||||
|
// and events that were processed and EVENT_GC_TIMEOUT passed after
|
||||||
|
// so sending instance has time to react to those events.
|
||||||
|
if ((evt->processed && difftime(time(0), evt->processed) > EVENT_GC_TIMEOUT) ||
|
||||||
|
(!evt->processed && difftime(time(0), evt->posted) > EVENT_GC_TIMEOUT))
|
||||||
|
memset(evt, 0, sizeof(IPCEvent));
|
||||||
|
|
||||||
|
if (evt->posted && !evt->processed && evt->sender != getpid())
|
||||||
|
{
|
||||||
|
if (evt->dest == Settings::getInstance().getCurrentProfileId() || (evt->dest == 0 && isCurrentOwner()))
|
||||||
|
{
|
||||||
|
evt->processed = time(0);
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (QThread::currentThread() != qApp->thread())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "runEventHandler",
|
||||||
|
Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(bool, result),
|
||||||
|
Q_ARG(IPCEventHandler, handler),
|
||||||
|
Q_ARG(const QByteArray&, arg));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = handler(arg);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IPC::processEvents()
|
void IPC::processEvents()
|
||||||
{
|
{
|
||||||
if (globalMemory.lock())
|
if (globalMemory.lock())
|
||||||
{
|
{
|
||||||
lastSeenTimestamp = getGlobalTimestamp();
|
IPCMemory* mem = global();
|
||||||
|
|
||||||
// Only the owner processes events. But if the previous owner's dead, we can take ownership now
|
if (mem->globalId == globalId)
|
||||||
if (*(uint64_t*)globalMemory.data() != globalId)
|
|
||||||
{
|
{
|
||||||
if (difftime(time(0), getGlobalTimestamp()) >= OWNERSHIP_TIMEOUT_S)
|
// We're the owner, let's process those events
|
||||||
|
mem->lastProcessed = time(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Only the owner processes events. But if the previous owner's dead, we can take ownership now
|
||||||
|
if (difftime(time(0), mem->lastProcessed) >= OWNERSHIP_TIMEOUT_S)
|
||||||
{
|
{
|
||||||
qDebug() << "IPC: Previous owner timed out, taking ownership";
|
qDebug() << "IPC: Previous owner timed out, taking ownership" << mem->globalId << "->" << globalId;
|
||||||
*(uint64_t*)globalMemory.data() = globalId;
|
// Ignore events that were not meant for this instance
|
||||||
|
memset(mem, 0, sizeof(IPCMemory));
|
||||||
|
mem->globalId = globalId;
|
||||||
|
mem->lastProcessed = time(0);
|
||||||
}
|
}
|
||||||
else
|
// Non-main instance is limited to events destined for specific profile it runs
|
||||||
|
}
|
||||||
|
|
||||||
|
while (IPCEvent* evt = fetchEvent())
|
||||||
|
{
|
||||||
|
QString name = QString::fromUtf8(evt->name);
|
||||||
|
auto it = eventHandlers.find(name);
|
||||||
|
if (it != eventHandlers.end())
|
||||||
{
|
{
|
||||||
goto unlockAndRestartTimer;
|
evt->accepted = runEventHandler(it.value(), evt->data);
|
||||||
|
qDebug() << "IPC: Processing event: " << name << ":" << evt->posted << "=" << evt->accepted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're the owner, let's process those events
|
|
||||||
forever {
|
|
||||||
QByteArray eventData = fetchEvent();
|
|
||||||
if (eventData.isEmpty())
|
|
||||||
break;
|
|
||||||
|
|
||||||
qDebug() << "IPC: Processing event: "<<eventData;
|
|
||||||
for (const IPCEventHandler& handler : eventHandlers)
|
|
||||||
runEventHandler(handler, eventData);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGlobalTimestamp();
|
|
||||||
goto unlockAndRestartTimer;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//qWarning() << "IPC:processEvents failed to lock";
|
|
||||||
goto restartTimer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Centralized cleanup. Always restart the timer, unlock only if we locked successfully.
|
|
||||||
unlockAndRestartTimer:
|
|
||||||
globalMemory.unlock();
|
|
||||||
restartTimer:
|
|
||||||
ownerTimer.start();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
time_t IPC::postEvent(const QByteArray& data)
|
|
||||||
{
|
|
||||||
int dataSize = data.size();
|
|
||||||
if (dataSize >= 65535)
|
|
||||||
{
|
|
||||||
qWarning() << "IPC: sendEvent: Too much data for a single chunk, giving up";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (globalMemory.lock())
|
|
||||||
{
|
|
||||||
// Check that we have enough room for that new chunk
|
|
||||||
char* nextChunk = getFirstFreeChunk();
|
|
||||||
if (nextChunk == nullptr
|
|
||||||
|| nextChunk + 2 + dataSize + 2 - (char*)globalMemory.data() >= MEMORY_SIZE)
|
|
||||||
{
|
|
||||||
qWarning() << "IPC: sendEvent: Not enough memory left, giving up";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the new chunk to shared memory
|
|
||||||
*(uint16_t*)nextChunk = dataSize;
|
|
||||||
memcpy(nextChunk+2, data.data(), dataSize);
|
|
||||||
*(uint16_t*)(nextChunk+2+dataSize) = 0;
|
|
||||||
|
|
||||||
globalMemory.unlock();
|
globalMemory.unlock();
|
||||||
qDebug() << "IPC: Posted event: "<<data;
|
|
||||||
return time(0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qWarning() << "IPC: sendEvent failed to lock, giving up";
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
timer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
char* IPC::getFirstFreeChunk()
|
IPC::IPCMemory *IPC::global()
|
||||||
{
|
{
|
||||||
char* ptr = (char*)globalMemory.data() + MEMORY_HEADER_SIZE;
|
return (IPCMemory*)globalMemory.data();
|
||||||
|
|
||||||
forever
|
|
||||||
{
|
|
||||||
uint16_t chunkSize = *(uint16_t*)ptr;
|
|
||||||
|
|
||||||
if (!chunkSize)
|
|
||||||
return ptr;
|
|
||||||
|
|
||||||
if ((ptr + chunkSize + 2) - (char*)globalMemory.data() >= MEMORY_SIZE)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
ptr += chunkSize;
|
|
||||||
}
|
|
||||||
return nullptr; // Never reached
|
|
||||||
}
|
|
||||||
|
|
||||||
char* IPC::getLastUsedChunk()
|
|
||||||
{
|
|
||||||
char* ptr = (char*)globalMemory.data() + MEMORY_HEADER_SIZE;
|
|
||||||
char* lastPtr = nullptr;
|
|
||||||
|
|
||||||
forever
|
|
||||||
{
|
|
||||||
uint16_t chunkSize = *(uint16_t*)ptr;
|
|
||||||
|
|
||||||
if (!chunkSize)
|
|
||||||
return lastPtr;
|
|
||||||
|
|
||||||
if ((ptr + chunkSize + 2) - (char*)globalMemory.data() > MEMORY_SIZE)
|
|
||||||
return lastPtr;
|
|
||||||
|
|
||||||
lastPtr = ptr;
|
|
||||||
ptr += chunkSize;
|
|
||||||
}
|
|
||||||
return nullptr; // Never reached
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray IPC::fetchEvent()
|
|
||||||
{
|
|
||||||
QByteArray eventData;
|
|
||||||
|
|
||||||
// Get a pointer to the last chunk
|
|
||||||
char* nextChunk = getLastUsedChunk();
|
|
||||||
if (nextChunk == nullptr)
|
|
||||||
return eventData;
|
|
||||||
|
|
||||||
// Read that chunk and remove it from memory
|
|
||||||
uint16_t dataSize = *(uint16_t*)nextChunk;
|
|
||||||
*(uint16_t*)nextChunk = 0;
|
|
||||||
eventData.resize(dataSize);
|
|
||||||
memcpy(eventData.data(), nextChunk+2, dataSize);
|
|
||||||
|
|
||||||
return eventData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IPC::updateGlobalTimestamp()
|
|
||||||
{
|
|
||||||
*(time_t*)((char*)globalMemory.data()+sizeof(globalId)) = time(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
time_t IPC::getGlobalTimestamp()
|
|
||||||
{
|
|
||||||
return *(time_t*)((char*)globalMemory.data()+sizeof(globalId));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IPC::isEventProcessed(time_t postTime)
|
|
||||||
{
|
|
||||||
return (difftime(lastSeenTimestamp, postTime) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IPC::waitUntilProcessed(time_t postTime)
|
|
||||||
{
|
|
||||||
while (difftime(lastSeenTimestamp, postTime) <= 0)
|
|
||||||
qApp->processEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg)
|
|
||||||
{
|
|
||||||
if (QThread::currentThread() != qApp->thread())
|
|
||||||
{
|
|
||||||
QMetaObject::invokeMethod(this, "runEventHandler", Qt::BlockingQueuedConnection,
|
|
||||||
Q_ARG(IPCEventHandler, handler), Q_ARG(const QByteArray&, arg));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
handler(arg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
87
src/ipc.h
87
src/ipc.h
|
@ -23,62 +23,71 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include <QMap>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
/// Handles an IPC event, must filter out and ignore events it doesn't recognize
|
using IPCEventHandler = std::function<bool (const QByteArray&)>;
|
||||||
using IPCEventHandler = std::function<void (const QByteArray&)>;
|
|
||||||
|
#define IPC_PROTOCOL_VERSION "2"
|
||||||
|
|
||||||
/// Class used for inter-process communication with other qTox instances
|
|
||||||
/// IPC event handlers will be called from the GUI thread after its event loop starts
|
|
||||||
class IPC : public QThread
|
class IPC : public QThread
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
static const int EVENT_TIMER_MS = 1000;
|
||||||
|
static const int EVENT_GC_TIMEOUT = 5;
|
||||||
|
static const int EVENT_QUEUE_SIZE = 32;
|
||||||
|
static const int OWNERSHIP_TIMEOUT_S = 5;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IPC();
|
IPC();
|
||||||
~IPC();
|
~IPC();
|
||||||
/// Posts an event to the global shared memory, returns the time at wich it was posted
|
|
||||||
time_t postEvent(const QByteArray& data);
|
struct IPCEvent
|
||||||
bool isCurrentOwner(); ///< Returns whether we're responsible for event processing of the global shared memory
|
{
|
||||||
void registerEventHandler(IPCEventHandler handler); ///< Registers a function to be called whenever an event is received
|
uint32_t dest;
|
||||||
bool isEventProcessed(time_t postTime); ///< Returns wether a previously posted event was already processed
|
int32_t sender;
|
||||||
void waitUntilProcessed(time_t postTime); ///< Blocks until a previously posted event is processed
|
char name[16];
|
||||||
|
char data[128];
|
||||||
|
time_t posted;
|
||||||
|
time_t processed;
|
||||||
|
uint32_t flags;
|
||||||
|
bool accepted;
|
||||||
|
bool global;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IPCMemory
|
||||||
|
{
|
||||||
|
uint64_t globalId;
|
||||||
|
// When last event was posted
|
||||||
|
time_t lastEvent;
|
||||||
|
// When processEvents() ran last time
|
||||||
|
time_t lastProcessed;
|
||||||
|
IPCEvent events[IPC::EVENT_QUEUE_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
// dest: Settings::getCurrentProfileId() or 0 (main instance).
|
||||||
|
time_t postEvent(const QString& name, const QByteArray &data=QByteArray(), uint32_t dest=0);
|
||||||
|
bool isCurrentOwner();
|
||||||
|
void registerEventHandler(const QString& name, IPCEventHandler handler);
|
||||||
|
bool isEventProcessed(time_t time);
|
||||||
|
bool isEventAccepted(time_t time);
|
||||||
|
bool waitUntilProcessed(time_t time, int32_t timeout=-1);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void processEvents();
|
void processEvents();
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
/// Runs an IPC event handler from the main (GUI) thread, will block until the handler returns
|
IPCMemory* global();
|
||||||
Q_INVOKABLE void runEventHandler(IPCEventHandler handler, const QByteArray& arg);
|
bool runEventHandler(IPCEventHandler handler, const QByteArray& arg);
|
||||||
/// Assumes that the memory IS LOCKED
|
// Only called when global memory IS LOCKED, returns 0 if no evnts present
|
||||||
/// Returns a pointer to the first free chunk of shared memory or a nullptr on error
|
IPCEvent* fetchEvent();
|
||||||
char* getFirstFreeChunk();
|
|
||||||
/// Assumes that the memory IS LOCKED
|
|
||||||
/// Returns a pointer to the last used chunk of shared memory or a nullptr on error
|
|
||||||
char* getLastUsedChunk();
|
|
||||||
/// Assumes that the memory IS LOCKED
|
|
||||||
/// Removes the last event from the shared memory and returns its data, or an empty object on error
|
|
||||||
QByteArray fetchEvent();
|
|
||||||
/// Assumes that the memory IS LOCKED
|
|
||||||
/// Updates the global shared timestamp
|
|
||||||
void updateGlobalTimestamp();
|
|
||||||
/// Assumes that the memory IS LOCKED
|
|
||||||
/// Returns the global shared timestamp
|
|
||||||
time_t getGlobalTimestamp();
|
|
||||||
|
|
||||||
private:
|
QTimer timer;
|
||||||
QSharedMemory globalMemory;
|
|
||||||
uint64_t globalId;
|
uint64_t globalId;
|
||||||
QTimer ownerTimer;
|
QSharedMemory globalMemory;
|
||||||
QVector<IPCEventHandler> eventHandlers;
|
QMap<QString, IPCEventHandler> eventHandlers;
|
||||||
time_t lastSeenTimestamp;
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
static const int MEMORY_SIZE = 4096;
|
|
||||||
static const int MEMORY_HEADER_SIZE = sizeof(globalId) + sizeof(time_t);
|
|
||||||
static const int EVENT_TIMER_MS = 1000;
|
|
||||||
static const int OWNERSHIP_TIMEOUT_S = 5;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IPC_H
|
#endif // IPC_H
|
||||||
|
|
15
src/main.cpp
15
src/main.cpp
|
@ -123,14 +123,13 @@ int main(int argc, char *argv[])
|
||||||
AutoUpdater::installLocalUpdate(); ///< NORETURN
|
AutoUpdater::installLocalUpdate(); ///< NORETURN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Nexus::getInstance().start();
|
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
// Inter-process communication
|
// Inter-process communication
|
||||||
IPC ipc;
|
IPC ipc;
|
||||||
ipc.registerEventHandler(&toxURIEventHandler);
|
ipc.registerEventHandler("uri", &toxURIEventHandler);
|
||||||
ipc.registerEventHandler(&toxSaveEventHandler);
|
ipc.registerEventHandler("save", &toxSaveEventHandler);
|
||||||
ipc.registerEventHandler(&toxActivateEventHandler);
|
ipc.registerEventHandler("activate", &toxActivateEventHandler);
|
||||||
|
|
||||||
if (parser.positionalArguments().size() > 0)
|
if (parser.positionalArguments().size() > 0)
|
||||||
{
|
{
|
||||||
|
@ -145,7 +144,7 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
time_t event = ipc.postEvent(firstParam.toUtf8());
|
time_t event = ipc.postEvent("uri", firstParam.toUtf8());
|
||||||
ipc.waitUntilProcessed(event);
|
ipc.waitUntilProcessed(event);
|
||||||
// If someone else processed it, we're done here, no need to actually start qTox
|
// If someone else processed it, we're done here, no need to actually start qTox
|
||||||
if (!ipc.isCurrentOwner())
|
if (!ipc.isCurrentOwner())
|
||||||
|
@ -160,7 +159,7 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
time_t event = ipc.postEvent(firstParam.toUtf8());
|
time_t event = ipc.postEvent("save", firstParam.toUtf8());
|
||||||
ipc.waitUntilProcessed(event);
|
ipc.waitUntilProcessed(event);
|
||||||
// If someone else processed it, we're done here, no need to actually start qTox
|
// If someone else processed it, we're done here, no need to actually start qTox
|
||||||
if (!ipc.isCurrentOwner())
|
if (!ipc.isCurrentOwner())
|
||||||
|
@ -175,13 +174,15 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
else if (!ipc.isCurrentOwner() && !parser.isSet("p"))
|
else if (!ipc.isCurrentOwner() && !parser.isSet("p"))
|
||||||
{
|
{
|
||||||
time_t event = ipc.postEvent("$activate");
|
time_t event = ipc.postEvent("activate");
|
||||||
ipc.waitUntilProcessed(event);
|
ipc.waitUntilProcessed(event);
|
||||||
if (!ipc.isCurrentOwner())
|
if (!ipc.isCurrentOwner())
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Nexus::getInstance().start();
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
a.setQuitOnLastWindowClosed(false);
|
a.setQuitOnLastWindowClosed(false);
|
||||||
int errorcode = a.exec();
|
int errorcode = a.exec();
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QStyleFactory>
|
#include <QStyleFactory>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
|
||||||
|
|
||||||
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
|
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
|
||||||
|
|
||||||
|
@ -42,7 +44,7 @@ Settings* Settings::settings{nullptr};
|
||||||
bool Settings::makeToxPortable{false};
|
bool Settings::makeToxPortable{false};
|
||||||
|
|
||||||
Settings::Settings() :
|
Settings::Settings() :
|
||||||
loaded(false), useCustomDhtList{false}
|
loaded(false), useCustomDhtList{false}, currentProfileId(0)
|
||||||
{
|
{
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
@ -54,20 +56,16 @@ Settings& Settings::getInstance()
|
||||||
return *settings;
|
return *settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::resetInstance()
|
|
||||||
{
|
|
||||||
if (settings)
|
|
||||||
{
|
|
||||||
delete settings;
|
|
||||||
settings = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::switchProfile(const QString& profile)
|
void Settings::switchProfile(const QString& profile)
|
||||||
{
|
{
|
||||||
|
// Saves current profile as main profile if this instance is main instance
|
||||||
setCurrentProfile(profile);
|
setCurrentProfile(profile);
|
||||||
save(false);
|
save(false);
|
||||||
resetInstance();
|
|
||||||
|
// If this instance is not main instance previous save did not happen therefore
|
||||||
|
// we manually set profile again and load profile settings
|
||||||
|
setCurrentProfile(profile);
|
||||||
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::detectProfile()
|
QString Settings::detectProfile()
|
||||||
|
@ -205,6 +203,7 @@ void Settings::load()
|
||||||
proxyAddr = s.value("proxyAddr", "").toString();
|
proxyAddr = s.value("proxyAddr", "").toString();
|
||||||
proxyPort = s.value("proxyPort", 0).toInt();
|
proxyPort = s.value("proxyPort", 0).toInt();
|
||||||
currentProfile = s.value("currentProfile", "").toString();
|
currentProfile = s.value("currentProfile", "").toString();
|
||||||
|
currentProfileId = makeProfileId(currentProfile);
|
||||||
autoAwayTime = s.value("autoAwayTime", 10).toInt();
|
autoAwayTime = s.value("autoAwayTime", 10).toInt();
|
||||||
checkUpdates = s.value("checkUpdates", false).toBool();
|
checkUpdates = s.value("checkUpdates", false).toBool();
|
||||||
showWindow = s.value("showWindow", true).toBool();
|
showWindow = s.value("showWindow", true).toBool();
|
||||||
|
@ -448,6 +447,13 @@ void Settings::save(QString path, bool writePersonal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t Settings::makeProfileId(const QString& profile)
|
||||||
|
{
|
||||||
|
QByteArray data = QCryptographicHash::hash(profile.toUtf8(), QCryptographicHash::Md5);
|
||||||
|
const uint32_t* dwords = (uint32_t*)data.constData();
|
||||||
|
return dwords[0] ^ dwords[1] ^ dwords[2] ^ dwords[3];
|
||||||
|
}
|
||||||
|
|
||||||
QString Settings::getSettingsDirPath()
|
QString Settings::getSettingsDirPath()
|
||||||
{
|
{
|
||||||
if (makeToxPortable)
|
if (makeToxPortable)
|
||||||
|
@ -733,9 +739,15 @@ QString Settings::getCurrentProfile() const
|
||||||
return currentProfile;
|
return currentProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t Settings::getCurrentProfileId() const
|
||||||
|
{
|
||||||
|
return currentProfileId;
|
||||||
|
}
|
||||||
|
|
||||||
void Settings::setCurrentProfile(QString profile)
|
void Settings::setCurrentProfile(QString profile)
|
||||||
{
|
{
|
||||||
currentProfile = profile;
|
currentProfile = profile;
|
||||||
|
currentProfileId = makeProfileId(currentProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::getEnableLogging() const
|
bool Settings::getEnableLogging() const
|
||||||
|
|
|
@ -31,7 +31,6 @@ class Settings : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
static Settings& getInstance();
|
static Settings& getInstance();
|
||||||
static void resetInstance();
|
|
||||||
void switchProfile(const QString& profile);
|
void switchProfile(const QString& profile);
|
||||||
QString detectProfile();
|
QString detectProfile();
|
||||||
QList<QString> searchProfiles();
|
QList<QString> searchProfiles();
|
||||||
|
@ -84,6 +83,7 @@ public:
|
||||||
void setUseEmoticons(bool newValue);
|
void setUseEmoticons(bool newValue);
|
||||||
|
|
||||||
QString getCurrentProfile() const;
|
QString getCurrentProfile() const;
|
||||||
|
uint32_t getCurrentProfileId() const;
|
||||||
void setCurrentProfile(QString profile);
|
void setCurrentProfile(QString profile);
|
||||||
|
|
||||||
QString getTranslation() const;
|
QString getTranslation() const;
|
||||||
|
@ -253,6 +253,7 @@ private:
|
||||||
Settings();
|
Settings();
|
||||||
Settings(Settings &settings) = delete;
|
Settings(Settings &settings) = delete;
|
||||||
Settings& operator=(const Settings&) = delete;
|
Settings& operator=(const Settings&) = delete;
|
||||||
|
static uint32_t makeProfileId(const QString& profile);
|
||||||
|
|
||||||
static const QString FILENAME;
|
static const QString FILENAME;
|
||||||
static const QString OLDFILENAME;
|
static const QString OLDFILENAME;
|
||||||
|
@ -286,6 +287,7 @@ private:
|
||||||
int proxyPort;
|
int proxyPort;
|
||||||
|
|
||||||
QString currentProfile;
|
QString currentProfile;
|
||||||
|
uint32_t currentProfileId;
|
||||||
|
|
||||||
bool enableLogging;
|
bool enableLogging;
|
||||||
bool encryptLogs;
|
bool encryptLogs;
|
||||||
|
|
|
@ -22,15 +22,16 @@
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
||||||
void toxSaveEventHandler(const QByteArray& eventData)
|
bool toxSaveEventHandler(const QByteArray& eventData)
|
||||||
{
|
{
|
||||||
if (!eventData.endsWith(".tox"))
|
if (!eventData.endsWith(".tox"))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
handleToxSave(eventData);
|
handleToxSave(eventData);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleToxSave(const QString& path)
|
bool handleToxSave(const QString& path)
|
||||||
{
|
{
|
||||||
Core* core = Core::getInstance();
|
Core* core = Core::getInstance();
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ void handleToxSave(const QString& path)
|
||||||
|
|
||||||
QFileInfo info(path);
|
QFileInfo info(path);
|
||||||
if (!info.exists())
|
if (!info.exists())
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
QString profile = info.completeBaseName();
|
QString profile = info.completeBaseName();
|
||||||
|
|
||||||
|
@ -55,17 +56,18 @@ void handleToxSave(const QString& path)
|
||||||
{
|
{
|
||||||
GUI::showWarning(QObject::tr("Ignoring non-Tox file", "popup title"),
|
GUI::showWarning(QObject::tr("Ignoring non-Tox file", "popup title"),
|
||||||
QObject::tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text"));
|
QObject::tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text"));
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT);
|
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT);
|
||||||
|
|
||||||
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(QObject::tr("Profile already exists", "import confirm title"),
|
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(QObject::tr("Profile already exists", "import confirm title"),
|
||||||
QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
|
QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
QFile::copy(path, profilePath);
|
QFile::copy(path, profilePath);
|
||||||
// no good way to update the ui from here... maybe we need a Widget:refreshUi() function...
|
// no good way to update the ui from here... maybe we need a Widget:refreshUi() function...
|
||||||
// such a thing would simplify other code as well I believe
|
// such a thing would simplify other code as well I believe
|
||||||
GUI::showInfo(QObject::tr("Profile imported"), QObject::tr("%1.tox was successfully imported").arg(profile));
|
GUI::showInfo(QObject::tr("Profile imported"), QObject::tr("%1.tox was successfully imported").arg(profile));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,9 @@ class QString;
|
||||||
class QByteArray;
|
class QByteArray;
|
||||||
|
|
||||||
/// Will wait until the core is ready first
|
/// Will wait until the core is ready first
|
||||||
void handleToxSave(const QString& path);
|
bool handleToxSave(const QString& path);
|
||||||
|
|
||||||
// Internals
|
// Internals
|
||||||
void toxSaveEventHandler(const QByteArray& eventData);
|
bool toxSaveEventHandler(const QByteArray& eventData);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -30,15 +30,16 @@
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
void toxURIEventHandler(const QByteArray& eventData)
|
bool toxURIEventHandler(const QByteArray& eventData)
|
||||||
{
|
{
|
||||||
if (!eventData.startsWith("tox:"))
|
if (!eventData.startsWith("tox:"))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
handleToxURI(eventData);
|
handleToxURI(eventData);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleToxURI(const QString &toxURI)
|
bool handleToxURI(const QString &toxURI)
|
||||||
{
|
{
|
||||||
Core* core = Core::getInstance();
|
Core* core = Core::getInstance();
|
||||||
|
|
||||||
|
@ -71,6 +72,7 @@ void handleToxURI(const QString &toxURI)
|
||||||
if (dialog.exec() == QDialog::Accepted)
|
if (dialog.exec() == QDialog::Accepted)
|
||||||
Core::getInstance()->requestFriendship(toxid, dialog.getRequestMessage());
|
Core::getInstance()->requestFriendship(toxid, dialog.getRequestMessage());
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ToxURIDialog::ToxURIDialog(QWidget *parent, const QString &userId, const QString &message) :
|
ToxURIDialog::ToxURIDialog(QWidget *parent, const QString &userId, const QString &message) :
|
||||||
|
|
|
@ -22,12 +22,12 @@
|
||||||
|
|
||||||
/// Shows a dialog asking whether or not to add this tox address as a friend
|
/// Shows a dialog asking whether or not to add this tox address as a friend
|
||||||
/// Will wait until the core is ready first
|
/// Will wait until the core is ready first
|
||||||
void handleToxURI(const QString& toxURI);
|
bool handleToxURI(const QString& toxURI);
|
||||||
|
|
||||||
// Internals
|
// Internals
|
||||||
class QByteArray;
|
class QByteArray;
|
||||||
class QPlainTextEdit;
|
class QPlainTextEdit;
|
||||||
void toxURIEventHandler(const QByteArray& eventData);
|
bool toxURIEventHandler(const QByteArray& eventData);
|
||||||
class ToxURIDialog : public QDialog
|
class ToxURIDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
|
@ -67,11 +67,11 @@
|
||||||
#define IS_ON_DESKTOP_GUI 1
|
#define IS_ON_DESKTOP_GUI 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void toxActivateEventHandler(const QByteArray& data)
|
bool toxActivateEventHandler(const QByteArray&)
|
||||||
{
|
{
|
||||||
if (data != "$activate")
|
if (!Widget::getInstance()->isActiveWindow())
|
||||||
return;
|
Widget::getInstance()->forceShow();
|
||||||
Widget::getInstance()->forceShow();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget *Widget::instance{nullptr};
|
Widget *Widget::instance{nullptr};
|
||||||
|
|
|
@ -170,6 +170,6 @@ private:
|
||||||
bool eventIcon;
|
bool eventIcon;
|
||||||
};
|
};
|
||||||
|
|
||||||
void toxActivateEventHandler(const QByteArray& data);
|
bool toxActivateEventHandler(const QByteArray& data);
|
||||||
|
|
||||||
#endif // WIDGET_H
|
#endif // WIDGET_H
|
||||||
|
|
Loading…
Reference in New Issue
Block a user