mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge branch 'pr1243'
This commit is contained in:
commit
1a1debf167
388
src/ipc.cpp
388
src/ipc.cpp
|
@ -16,37 +16,39 @@
|
|||
|
||||
|
||||
#include "src/ipc.h"
|
||||
#include "src/misc/settings.h"
|
||||
#include <QDebug>
|
||||
#include <QCoreApplication>
|
||||
#include <unistd.h>
|
||||
|
||||
IPC::IPC() :
|
||||
globalMemory{"qtox"}
|
||||
|
||||
IPC::IPC()
|
||||
: globalMemory{"qtox-" IPC_PROTOCOL_VERSION}
|
||||
{
|
||||
qRegisterMetaType<IPCEventHandler>("IPCEventHandler");
|
||||
|
||||
ownerTimer.setInterval(EVENT_TIMER_MS);
|
||||
ownerTimer.setSingleShot(true);
|
||||
connect(&ownerTimer, &QTimer::timeout, this, &IPC::processEvents);
|
||||
timer.setInterval(EVENT_TIMER_MS);
|
||||
timer.setSingleShot(true);
|
||||
connect(&timer, &QTimer::timeout, this, &IPC::processEvents);
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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));
|
||||
globalId = ((uint64_t)qrand()) * ((uint64_t)qrand()) * ((uint64_t)qrand());
|
||||
qDebug() << "IPC: Our global ID is "<<globalId;
|
||||
if (globalMemory.create(MEMORY_SIZE))
|
||||
qDebug() << "IPC: Our global ID is " << globalId;
|
||||
if (globalMemory.create(sizeof(IPCMemory)))
|
||||
{
|
||||
qDebug() << "IPC: Creating the global shared memory and taking ownership";
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
*(uint64_t*)globalMemory.data() = globalId;
|
||||
updateGlobalTimestamp();
|
||||
IPCMemory* mem = global();
|
||||
memset(mem, 0, sizeof(IPCMemory));
|
||||
mem->globalId = globalId;
|
||||
mem->lastProcessed = time(0);
|
||||
globalMemory.unlock();
|
||||
}
|
||||
else
|
||||
|
@ -64,18 +66,68 @@ IPC::IPC() :
|
|||
return; // We won't be able to do any IPC without being attached, let's get outta here
|
||||
}
|
||||
|
||||
ownerTimer.start();
|
||||
timer.start();
|
||||
}
|
||||
|
||||
IPC::~IPC()
|
||||
{
|
||||
if (isCurrentOwner())
|
||||
{
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
global()->globalId = 0;
|
||||
globalMemory.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IPC& IPC::getInstance()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
Q_ASSERT(0 && "IPC can not be used on android");
|
||||
#endif
|
||||
static IPC instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
char* data = (char*)globalMemory.data();
|
||||
if (data)
|
||||
*(time_t*)(data+sizeof(globalId)) = 0;
|
||||
IPCEvent* evt = 0;
|
||||
IPCMemory* mem = global();
|
||||
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();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
qDebug() << "IPC: Failed to lock in postEvent()";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool IPC::isCurrentOwner()
|
||||
|
@ -88,190 +140,168 @@ bool IPC::isCurrentOwner()
|
|||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "IPC:isCurrentOwner failed to lock, returning false";
|
||||
qWarning() << "IPC: isCurrentOwner failed to lock, returning 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()
|
||||
{
|
||||
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 (*(uint64_t*)globalMemory.data() != globalId)
|
||||
if (mem->globalId == 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";
|
||||
*(uint64_t*)globalMemory.data() = globalId;
|
||||
qDebug() << "IPC: Previous owner timed out, taking ownership" << mem->globalId << "->" << 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();
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
return (IPCMemory*)globalMemory.data();
|
||||
}
|
||||
|
|
91
src/ipc.h
91
src/ipc.h
|
@ -23,62 +23,73 @@
|
|||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
#include <QMap>
|
||||
#include <functional>
|
||||
#include <ctime>
|
||||
|
||||
/// Handles an IPC event, must filter out and ignore events it doesn't recognize
|
||||
using IPCEventHandler = std::function<void (const QByteArray&)>;
|
||||
using IPCEventHandler = std::function<bool (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
|
||||
{
|
||||
Q_OBJECT
|
||||
IPC();
|
||||
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:
|
||||
IPC();
|
||||
~IPC();
|
||||
/// Posts an event to the global shared memory, returns the time at wich it was posted
|
||||
time_t postEvent(const QByteArray& data);
|
||||
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
|
||||
bool isEventProcessed(time_t postTime); ///< Returns wether a previously posted event was already processed
|
||||
void waitUntilProcessed(time_t postTime); ///< Blocks until a previously posted event is processed
|
||||
|
||||
static IPC& getInstance();
|
||||
|
||||
struct IPCEvent
|
||||
{
|
||||
uint32_t dest;
|
||||
int32_t sender;
|
||||
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:
|
||||
void processEvents();
|
||||
|
||||
private:
|
||||
/// Runs an IPC event handler from the main (GUI) thread, will block until the handler returns
|
||||
Q_INVOKABLE void runEventHandler(IPCEventHandler handler, const QByteArray& arg);
|
||||
/// Assumes that the memory IS LOCKED
|
||||
/// Returns a pointer to the first free chunk of shared memory or a nullptr on error
|
||||
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();
|
||||
protected:
|
||||
IPCMemory* global();
|
||||
bool runEventHandler(IPCEventHandler handler, const QByteArray& arg);
|
||||
// Only called when global memory IS LOCKED, returns 0 if no evnts present
|
||||
IPCEvent* fetchEvent();
|
||||
|
||||
private:
|
||||
QSharedMemory globalMemory;
|
||||
QTimer timer;
|
||||
uint64_t globalId;
|
||||
QTimer ownerTimer;
|
||||
QVector<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;
|
||||
QSharedMemory globalMemory;
|
||||
QMap<QString, IPCEventHandler> eventHandlers;
|
||||
};
|
||||
|
||||
#endif // IPC_H
|
||||
|
|
33
src/main.cpp
33
src/main.cpp
|
@ -72,6 +72,9 @@ int main(int argc, char *argv[])
|
|||
parser.addOption(QCommandLineOption("p", QObject::tr("Starts new instance and loads specified profile."), QObject::tr("profile")));
|
||||
parser.process(a);
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
IPC::getInstance();
|
||||
#endif
|
||||
Settings::getInstance(); // Build our Settings singleton as soon as QApplication is ready, not before
|
||||
|
||||
if (parser.isSet("p"))
|
||||
|
@ -125,14 +128,13 @@ int main(int argc, char *argv[])
|
|||
AutoUpdater::installLocalUpdate(); ///< NORETURN
|
||||
#endif
|
||||
|
||||
Nexus::getInstance().start();
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
// Inter-process communication
|
||||
IPC ipc;
|
||||
ipc.registerEventHandler(&toxURIEventHandler);
|
||||
ipc.registerEventHandler(&toxSaveEventHandler);
|
||||
ipc.registerEventHandler(&toxActivateEventHandler);
|
||||
IPC& ipc = IPC::getInstance();
|
||||
ipc.registerEventHandler("uri", &toxURIEventHandler);
|
||||
ipc.registerEventHandler("save", &toxSaveEventHandler);
|
||||
ipc.registerEventHandler("activate", &toxActivateEventHandler);
|
||||
|
||||
if (parser.positionalArguments().size() > 0)
|
||||
{
|
||||
|
@ -147,7 +149,7 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
else
|
||||
{
|
||||
time_t event = ipc.postEvent(firstParam.toUtf8());
|
||||
time_t event = ipc.postEvent("uri", firstParam.toUtf8());
|
||||
ipc.waitUntilProcessed(event);
|
||||
// If someone else processed it, we're done here, no need to actually start qTox
|
||||
if (!ipc.isCurrentOwner())
|
||||
|
@ -162,7 +164,7 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
else
|
||||
{
|
||||
time_t event = ipc.postEvent(firstParam.toUtf8());
|
||||
time_t event = ipc.postEvent("save", firstParam.toUtf8());
|
||||
ipc.waitUntilProcessed(event);
|
||||
// If someone else processed it, we're done here, no need to actually start qTox
|
||||
if (!ipc.isCurrentOwner())
|
||||
|
@ -175,15 +177,22 @@ int main(int argc, char *argv[])
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
else if (!ipc.isCurrentOwner() && !parser.isSet("p"))
|
||||
else if (!ipc.isCurrentOwner())
|
||||
{
|
||||
time_t event = ipc.postEvent("$activate");
|
||||
ipc.waitUntilProcessed(event);
|
||||
if (!ipc.isCurrentOwner())
|
||||
return EXIT_SUCCESS;
|
||||
uint32_t dest = 0;
|
||||
if (parser.isSet("p"))
|
||||
dest = Settings::getInstance().getCurrentProfileId();
|
||||
time_t event = ipc.postEvent("activate", QByteArray(), dest);
|
||||
if (ipc.waitUntilProcessed(event, 2) && ipc.isEventAccepted(event))
|
||||
{
|
||||
if (!ipc.isCurrentOwner())
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Nexus::getInstance().start();
|
||||
|
||||
// Run
|
||||
a.setQuitOnLastWindowClosed(false);
|
||||
int errorcode = a.exec();
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#ifdef QTOX_PLATFORM_EXT
|
||||
#include "src/platform/autorun.h"
|
||||
#endif
|
||||
#include "src/ipc.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QApplication>
|
||||
|
@ -33,6 +34,8 @@
|
|||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QStyleFactory>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
|
||||
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
|
||||
|
||||
|
@ -42,7 +45,7 @@ Settings* Settings::settings{nullptr};
|
|||
bool Settings::makeToxPortable{false};
|
||||
|
||||
Settings::Settings() :
|
||||
loaded(false), useCustomDhtList{false}
|
||||
loaded(false), useCustomDhtList{false}, currentProfileId(0)
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
@ -54,20 +57,16 @@ Settings& Settings::getInstance()
|
|||
return *settings;
|
||||
}
|
||||
|
||||
void Settings::resetInstance()
|
||||
{
|
||||
if (settings)
|
||||
{
|
||||
delete settings;
|
||||
settings = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::switchProfile(const QString& profile)
|
||||
{
|
||||
// Saves current profile as main profile if this instance is main instance
|
||||
setCurrentProfile(profile);
|
||||
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()
|
||||
|
@ -205,6 +204,7 @@ void Settings::load()
|
|||
proxyAddr = s.value("proxyAddr", "").toString();
|
||||
proxyPort = s.value("proxyPort", 0).toInt();
|
||||
currentProfile = s.value("currentProfile", "").toString();
|
||||
currentProfileId = makeProfileId(currentProfile);
|
||||
autoAwayTime = s.value("autoAwayTime", 10).toInt();
|
||||
checkUpdates = s.value("checkUpdates", false).toBool();
|
||||
showWindow = s.value("showWindow", true).toBool();
|
||||
|
@ -334,7 +334,18 @@ void Settings::save(bool writePersonal)
|
|||
|
||||
void Settings::save(QString path, bool writePersonal)
|
||||
{
|
||||
qDebug() << "Settings: Saving in "<<path;
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (IPC::getInstance().isCurrentOwner())
|
||||
#endif
|
||||
saveGlobal(path);
|
||||
|
||||
if (writePersonal) // Core::switchConfiguration
|
||||
savePersonal(path);
|
||||
}
|
||||
|
||||
void Settings::saveGlobal(QString path)
|
||||
{
|
||||
qDebug() << "Settings: Saving in " << path;
|
||||
|
||||
QSettings s(path, QSettings::IniFormat);
|
||||
|
||||
|
@ -421,31 +432,46 @@ void Settings::save(QString path, bool writePersonal)
|
|||
s.beginGroup("Video");
|
||||
s.setValue("camVideoRes",camVideoRes);
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
if (writePersonal && !currentProfile.isEmpty()) // Core::switchConfiguration
|
||||
void Settings::savePersonal(QString path)
|
||||
{
|
||||
if (currentProfile.isEmpty())
|
||||
{
|
||||
QSettings ps(QFileInfo(path).dir().filePath(currentProfile + ".ini"), QSettings::IniFormat);
|
||||
ps.beginGroup("Friends");
|
||||
ps.beginWriteArray("Friend", friendLst.size());
|
||||
int index = 0;
|
||||
for (auto& frnd : friendLst)
|
||||
{
|
||||
ps.setArrayIndex(index);
|
||||
ps.setValue("addr", frnd.addr);
|
||||
ps.setValue("alias", frnd.alias);
|
||||
ps.setValue("autoAcceptDir", frnd.autoAcceptDir);
|
||||
index++;
|
||||
}
|
||||
ps.endArray();
|
||||
ps.endGroup();
|
||||
|
||||
ps.beginGroup("Privacy");
|
||||
ps.setValue("typingNotification", typingNotification);
|
||||
ps.setValue("enableLogging", enableLogging);
|
||||
ps.setValue("encryptLogs", encryptLogs);
|
||||
ps.setValue("encryptTox", encryptTox);
|
||||
ps.endGroup();
|
||||
qDebug() << "Settings: could not save personal settings because currentProfile profile is empty";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Settings: Saving personal in " << path;
|
||||
|
||||
QSettings ps(QFileInfo(path).dir().filePath(currentProfile + ".ini"), QSettings::IniFormat);
|
||||
ps.beginGroup("Friends");
|
||||
ps.beginWriteArray("Friend", friendLst.size());
|
||||
int index = 0;
|
||||
for (auto& frnd : friendLst)
|
||||
{
|
||||
ps.setArrayIndex(index);
|
||||
ps.setValue("addr", frnd.addr);
|
||||
ps.setValue("alias", frnd.alias);
|
||||
ps.setValue("autoAcceptDir", frnd.autoAcceptDir);
|
||||
index++;
|
||||
}
|
||||
ps.endArray();
|
||||
ps.endGroup();
|
||||
|
||||
ps.beginGroup("Privacy");
|
||||
ps.setValue("typingNotification", typingNotification);
|
||||
ps.setValue("enableLogging", enableLogging);
|
||||
ps.setValue("encryptLogs", encryptLogs);
|
||||
ps.setValue("encryptTox", encryptTox);
|
||||
ps.endGroup();
|
||||
}
|
||||
|
||||
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()
|
||||
|
@ -735,9 +761,15 @@ QString Settings::getCurrentProfile() const
|
|||
return currentProfile;
|
||||
}
|
||||
|
||||
uint32_t Settings::getCurrentProfileId() const
|
||||
{
|
||||
return currentProfileId;
|
||||
}
|
||||
|
||||
void Settings::setCurrentProfile(QString profile)
|
||||
{
|
||||
currentProfile = profile;
|
||||
currentProfileId = makeProfileId(currentProfile);
|
||||
}
|
||||
|
||||
bool Settings::getEnableLogging() const
|
||||
|
|
|
@ -31,7 +31,6 @@ class Settings : public QObject
|
|||
Q_OBJECT
|
||||
public:
|
||||
static Settings& getInstance();
|
||||
static void resetInstance();
|
||||
void switchProfile(const QString& profile);
|
||||
QString detectProfile();
|
||||
QList<QString> searchProfiles();
|
||||
|
@ -84,6 +83,7 @@ public:
|
|||
void setUseEmoticons(bool newValue);
|
||||
|
||||
QString getCurrentProfile() const;
|
||||
uint32_t getCurrentProfileId() const;
|
||||
void setCurrentProfile(QString profile);
|
||||
|
||||
QString getTranslation() const;
|
||||
|
@ -253,6 +253,9 @@ private:
|
|||
Settings();
|
||||
Settings(Settings &settings) = delete;
|
||||
Settings& operator=(const Settings&) = delete;
|
||||
static uint32_t makeProfileId(const QString& profile);
|
||||
void saveGlobal(QString path);
|
||||
void savePersonal(QString path);
|
||||
|
||||
static const QString FILENAME;
|
||||
static const QString OLDFILENAME;
|
||||
|
@ -286,6 +289,7 @@ private:
|
|||
int proxyPort;
|
||||
|
||||
QString currentProfile;
|
||||
uint32_t currentProfileId;
|
||||
|
||||
bool enableLogging;
|
||||
bool encryptLogs;
|
||||
|
|
|
@ -22,15 +22,16 @@
|
|||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
void toxSaveEventHandler(const QByteArray& eventData)
|
||||
bool toxSaveEventHandler(const QByteArray& eventData)
|
||||
{
|
||||
if (!eventData.endsWith(".tox"))
|
||||
return;
|
||||
return false;
|
||||
|
||||
handleToxSave(eventData);
|
||||
return true;
|
||||
}
|
||||
|
||||
void handleToxSave(const QString& path)
|
||||
bool handleToxSave(const QString& path)
|
||||
{
|
||||
Core* core = Core::getInstance();
|
||||
|
||||
|
@ -47,7 +48,7 @@ void handleToxSave(const QString& path)
|
|||
|
||||
QFileInfo info(path);
|
||||
if (!info.exists())
|
||||
return;
|
||||
return false;
|
||||
|
||||
QString profile = info.completeBaseName();
|
||||
|
||||
|
@ -55,17 +56,18 @@ void handleToxSave(const QString& path)
|
|||
{
|
||||
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"));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT);
|
||||
|
||||
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)))
|
||||
return;
|
||||
return false;
|
||||
|
||||
QFile::copy(path, profilePath);
|
||||
// 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
|
||||
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;
|
||||
|
||||
/// Will wait until the core is ready first
|
||||
void handleToxSave(const QString& path);
|
||||
bool handleToxSave(const QString& path);
|
||||
|
||||
// Internals
|
||||
void toxSaveEventHandler(const QByteArray& eventData);
|
||||
bool toxSaveEventHandler(const QByteArray& eventData);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -31,15 +31,16 @@
|
|||
#include <QPushButton>
|
||||
#include <QCoreApplication>
|
||||
|
||||
void toxURIEventHandler(const QByteArray& eventData)
|
||||
bool toxURIEventHandler(const QByteArray& eventData)
|
||||
{
|
||||
if (!eventData.startsWith("tox:"))
|
||||
return;
|
||||
return false;
|
||||
|
||||
handleToxURI(eventData);
|
||||
return true;
|
||||
}
|
||||
|
||||
void handleToxURI(const QString &toxURI)
|
||||
bool handleToxURI(const QString &toxURI)
|
||||
{
|
||||
Core* core = Core::getInstance();
|
||||
|
||||
|
@ -72,6 +73,7 @@ void handleToxURI(const QString &toxURI)
|
|||
if (dialog.exec() == QDialog::Accepted)
|
||||
Core::getInstance()->requestFriendship(toxid, dialog.getRequestMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
/// Will wait until the core is ready first
|
||||
void handleToxURI(const QString& toxURI);
|
||||
bool handleToxURI(const QString& toxURI);
|
||||
|
||||
// Internals
|
||||
class QByteArray;
|
||||
class QPlainTextEdit;
|
||||
void toxURIEventHandler(const QByteArray& eventData);
|
||||
bool toxURIEventHandler(const QByteArray& eventData);
|
||||
class ToxURIDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
|
@ -69,11 +69,11 @@
|
|||
#define IS_ON_DESKTOP_GUI 1
|
||||
#endif
|
||||
|
||||
void toxActivateEventHandler(const QByteArray& data)
|
||||
bool toxActivateEventHandler(const QByteArray&)
|
||||
{
|
||||
if (data != "$activate")
|
||||
return;
|
||||
Widget::getInstance()->forceShow();
|
||||
if (!Widget::getInstance()->isActiveWindow())
|
||||
Widget::getInstance()->forceShow();
|
||||
return true;
|
||||
}
|
||||
|
||||
Widget *Widget::instance{nullptr};
|
||||
|
|
|
@ -173,6 +173,6 @@ private:
|
|||
bool eventIcon;
|
||||
};
|
||||
|
||||
void toxActivateEventHandler(const QByteArray& data);
|
||||
bool toxActivateEventHandler(const QByteArray& data);
|
||||
|
||||
#endif // WIDGET_H
|
||||
|
|
Loading…
Reference in New Issue
Block a user