1
0
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:
novist 2015-02-20 14:26:41 +02:00
parent 244d6daca8
commit 9523484bfe
11 changed files with 304 additions and 255 deletions

View File

@ -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,59 @@ 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();
}
}
}
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 +131,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();
}

View File

@ -23,62 +23,71 @@
#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
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
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

View File

@ -123,14 +123,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.registerEventHandler("uri", &toxURIEventHandler);
ipc.registerEventHandler("save", &toxSaveEventHandler);
ipc.registerEventHandler("activate", &toxActivateEventHandler);
if (parser.positionalArguments().size() > 0)
{
@ -145,7 +144,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())
@ -160,7 +159,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,13 +174,15 @@ int main(int argc, char *argv[])
}
else if (!ipc.isCurrentOwner() && !parser.isSet("p"))
{
time_t event = ipc.postEvent("$activate");
time_t event = ipc.postEvent("activate");
ipc.waitUntilProcessed(event);
if (!ipc.isCurrentOwner())
return EXIT_SUCCESS;
}
#endif
Nexus::getInstance().start();
// Run
a.setQuitOnLastWindowClosed(false);
int errorcode = a.exec();

View File

@ -33,6 +33,8 @@
#include <QDebug>
#include <QList>
#include <QStyleFactory>
#include <QCryptographicHash>
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
@ -42,7 +44,7 @@ Settings* Settings::settings{nullptr};
bool Settings::makeToxPortable{false};
Settings::Settings() :
loaded(false), useCustomDhtList{false}
loaded(false), useCustomDhtList{false}, currentProfileId(0)
{
load();
}
@ -54,20 +56,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 +203,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();
@ -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()
{
if (makeToxPortable)
@ -733,9 +739,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

View File

@ -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,7 @@ private:
Settings();
Settings(Settings &settings) = delete;
Settings& operator=(const Settings&) = delete;
static uint32_t makeProfileId(const QString& profile);
static const QString FILENAME;
static const QString OLDFILENAME;
@ -286,6 +287,7 @@ private:
int proxyPort;
QString currentProfile;
uint32_t currentProfileId;
bool enableLogging;
bool encryptLogs;

View File

@ -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;
}

View File

@ -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

View File

@ -30,15 +30,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();
@ -71,6 +72,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) :

View File

@ -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

View File

@ -67,11 +67,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};

View File

@ -170,6 +170,6 @@ private:
bool eventIcon;
};
void toxActivateEventHandler(const QByteArray& data);
bool toxActivateEventHandler(const QByteArray& data);
#endif // WIDGET_H