mirror of https://github.com/qTox/qTox.git
15 changed files with 802 additions and 258 deletions
@ -0,0 +1,254 @@
@@ -0,0 +1,254 @@
|
||||
#include "src/ipc.h" |
||||
#include <QDebug> |
||||
#include <QCoreApplication> |
||||
|
||||
IPC::IPC() : |
||||
globalMemory{"qtox"} |
||||
{ |
||||
qRegisterMetaType<IPCEventHandler>("IPCEventHandler"); |
||||
|
||||
ownerTimer.setInterval(EVENT_TIMER_MS); |
||||
ownerTimer.setSingleShot(true); |
||||
connect(&ownerTimer, &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
|
||||
// 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: Creating the global shared memory and taking ownership"; |
||||
if (globalMemory.lock()) |
||||
{ |
||||
*(uint64_t*)globalMemory.data() = globalId; |
||||
updateGlobalTimestamp(); |
||||
globalMemory.unlock(); |
||||
} |
||||
else |
||||
{ |
||||
qWarning() << "IPC: Couldn't lock to take ownership"; |
||||
} |
||||
} |
||||
else if (globalMemory.attach()) |
||||
{ |
||||
qDebug() << "IPC: Attaching to the global shared memory"; |
||||
} |
||||
else |
||||
{ |
||||
qDebug() << "IPC: Failed to attach to the global shared memory, giving up"; |
||||
return; // We won't be able to do any IPC without being attached, let's get outta here
|
||||
} |
||||
|
||||
ownerTimer.start(); |
||||
} |
||||
|
||||
IPC::~IPC() |
||||
{ |
||||
|
||||
} |
||||
|
||||
bool IPC::isCurrentOwner() |
||||
{ |
||||
if (globalMemory.lock()) |
||||
{ |
||||
bool isOwner = ((*(uint64_t*)globalMemory.data()) == globalId); |
||||
globalMemory.unlock(); |
||||
return isOwner; |
||||
} |
||||
else |
||||
{ |
||||
qWarning() << "IPC:isCurrentOwner failed to lock, returning false"; |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
void IPC::registerEventHandler(IPCEventHandler handler) |
||||
{ |
||||
eventHandlers += handler; |
||||
} |
||||
|
||||
void IPC::processEvents() |
||||
{ |
||||
if (globalMemory.lock()) |
||||
{ |
||||
lastSeenTimestamp = getGlobalTimestamp(); |
||||
|
||||
// Only the owner processes events. But if the previous owner's dead, we can take ownership now
|
||||
if (*(uint64_t*)globalMemory.data() != globalId) |
||||
{ |
||||
if (difftime(time(0), getGlobalTimestamp()) >= OWNERSHIP_TIMEOUT_S) |
||||
{ |
||||
qDebug() << "IPC: Previous owner timed out, taking ownership"; |
||||
*(uint64_t*)globalMemory.data() = globalId; |
||||
} |
||||
else |
||||
{ |
||||
goto unlockAndRestartTimer; |
||||
} |
||||
} |
||||
|
||||
// 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; |
||||
} |
||||
} |
||||
|
||||
char* IPC::getFirstFreeChunk() |
||||
{ |
||||
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; |
||||
} |
||||
} |
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
#ifndef IPC_H |
||||
#define IPC_H |
||||
|
||||
#include <QSharedMemory> |
||||
#include <QTimer> |
||||
#include <QObject> |
||||
#include <QThread> |
||||
#include <QVector> |
||||
#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&)>; |
||||
|
||||
/// 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 |
||||
|
||||
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
|
||||
|
||||
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(); |
||||
|
||||
private: |
||||
QSharedMemory globalMemory; |
||||
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; |
||||
}; |
||||
|
||||
#endif // IPC_H
|
@ -0,0 +1,244 @@
@@ -0,0 +1,244 @@
|
||||
#include "src/toxdns.h" |
||||
#include "src/misc/cdata.h" |
||||
#include <QMessageBox> |
||||
#include <QCoreApplication> |
||||
#include <QThread> |
||||
#include <QDebug> |
||||
#include <tox/tox.h> |
||||
#include <tox/toxdns.h> |
||||
|
||||
#define TOX_ID_LENGTH 2*TOX_FRIEND_ADDRESS_SIZE |
||||
|
||||
const ToxDNS::tox3_server ToxDNS::pinnedServers[] |
||||
{ |
||||
{"toxme.se", (uint8_t[32]){0x5D, 0x72, 0xC5, 0x17, 0xDF, 0x6A, 0xEC, 0x54, 0xF1, 0xE9, 0x77, 0xA6, 0xB6, 0xF2, 0x59, 0x14, |
||||
0xEA, 0x4C, 0xF7, 0x27, 0x7A, 0x85, 0x02, 0x7C, 0xD9, 0xF5, 0x19, 0x6D, 0xF1, 0x7E, 0x0B, 0x13}}, |
||||
{"utox.org", (uint8_t[32]){0xD3, 0x15, 0x4F, 0x65, 0xD2, 0x8A, 0x5B, 0x41, 0xA0, 0x5D, 0x4A, 0xC7, 0xE4, 0xB3, 0x9C, 0x6B, |
||||
0x1C, 0x23, 0x3C, 0xC8, 0x57, 0xFB, 0x36, 0x5C, 0x56, 0xE8, 0x39, 0x27, 0x37, 0x46, 0x2A, 0x12}} |
||||
}; |
||||
|
||||
void ToxDNS::showWarning(const QString &message) |
||||
{ |
||||
QMessageBox warning; |
||||
warning.setWindowTitle("Tox"); |
||||
warning.setText(message); |
||||
warning.setIcon(QMessageBox::Warning); |
||||
warning.exec(); |
||||
} |
||||
|
||||
QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent) |
||||
{ |
||||
QByteArray result; |
||||
|
||||
QDnsLookup dns; |
||||
dns.setType(QDnsLookup::TXT); |
||||
dns.setName(record); |
||||
dns.lookup(); |
||||
|
||||
int timeout; |
||||
for (timeout = 0; timeout<30 && !dns.isFinished(); ++timeout) |
||||
{ |
||||
qApp->processEvents(); |
||||
QThread::msleep(100); |
||||
} |
||||
if (timeout >= 30) { |
||||
dns.abort(); |
||||
if (!silent) |
||||
showWarning(tr("The connection timed out","The DNS gives the Tox ID associated to toxme.se addresses")); |
||||
return result; |
||||
} |
||||
|
||||
if (dns.error() == QDnsLookup::NotFoundError) { |
||||
if (!silent) |
||||
showWarning(tr("This address does not exist","The DNS gives the Tox ID associated to toxme.se addresses")); |
||||
return result; |
||||
} |
||||
else if (dns.error() != QDnsLookup::NoError) { |
||||
if (!silent) |
||||
showWarning(tr("Error while looking up DNS","The DNS gives the Tox ID associated to toxme.se addresses")); |
||||
return result; |
||||
} |
||||
|
||||
const QList<QDnsTextRecord> textRecords = dns.textRecords(); |
||||
if (textRecords.isEmpty()) { |
||||
if (!silent) |
||||
showWarning(tr("No text record found", "Error with the DNS")); |
||||
return result; |
||||
} |
||||
|
||||
const QList<QByteArray> textRecordValues = textRecords.last().values(); |
||||
if (textRecordValues.length() != 1) { |
||||
if (!silent) |
||||
showWarning(tr("Unexpected number of values in text record", "Error with the DNS")); |
||||
return result; |
||||
} |
||||
|
||||
result = textRecordValues.first(); |
||||
return result; |
||||
} |
||||
|
||||
QString ToxDNS::queryTox1(const QString& record, bool silent) |
||||
{ |
||||
QString realRecord = record, toxId; |
||||
realRecord.replace("@", "._tox."); |
||||
const QString entry = fetchLastTextRecord(realRecord, silent); |
||||
if (entry.isEmpty()) |
||||
return toxId; |
||||
|
||||
// Check toxdns protocol version
|
||||
int verx = entry.indexOf("v="); |
||||
if (verx) { |
||||
verx += 2; |
||||
int verend = entry.indexOf(';', verx); |
||||
if (verend) |
||||
{ |
||||
QString ver = entry.mid(verx, verend-verx); |
||||
if (ver != "tox1") |
||||
{ |
||||
if (!silent) |
||||
showWarning(tr("The version of Tox DNS used by this server is not supported", "Error with the DNS")); |
||||
return toxId; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Get the tox id
|
||||
int idx = entry.indexOf("id="); |
||||
if (idx < 0) { |
||||
if (!silent) |
||||
showWarning(tr("The DNS lookup does not contain any Tox ID", "Error with the DNS")); |
||||
return toxId; |
||||
} |
||||
|
||||
idx += 3; |
||||
if (entry.length() < idx + static_cast<int>(TOX_ID_LENGTH)) { |
||||
if (!silent) |
||||
showWarning(tr("The DNS lookup does not contain a valid Tox ID", "Error with the DNS")); |
||||
return toxId; |
||||
} |
||||
|
||||
toxId = entry.mid(idx, TOX_ID_LENGTH); |
||||
if (!ToxID::isToxId(toxId)) { |
||||
if (!silent) |
||||
showWarning(tr("The DNS lookup does not contain a valid Tox ID", "Error with the DNS")); |
||||
return toxId; |
||||
} |
||||
|
||||
return toxId; |
||||
} |
||||
|
||||
QString ToxDNS::queryTox3(const tox3_server& server, const QString &record, bool silent) |
||||
{ |
||||
QByteArray nameData = record.left(record.indexOf('@')).toUtf8(), id, realRecord; |
||||
QString entry, toxIdStr; |
||||
int toxIdSize, idx, verx, dns_string_len; |
||||
const int dns_string_maxlen = 128; |
||||
|
||||
void* tox_dns3 = tox_dns3_new(server.pubkey); |
||||
if (!tox_dns3) |
||||
{ |
||||
qWarning() << "queryTox3: failed to create a tox_dns3 object for "<<server.name<<", using tox1 as a fallback"; |
||||
goto fallbackOnTox1; |
||||
} |
||||
uint32_t request_id; |
||||
uint8_t dns_string[dns_string_maxlen]; |
||||
dns_string_len = tox_generate_dns3_string(tox_dns3, dns_string, dns_string_maxlen, &request_id, |
||||
(uint8_t*)nameData.data(), nameData.size()); |
||||
|
||||
if (dns_string_len < 0) // We can always fallback on tox1 if toxdns3 fails
|
||||
{ |
||||
qWarning() << "queryTox3: failed to generate dns3 string for "<<server.name<<", using tox1 as a fallback"; |
||||
goto fallbackOnTox1; |
||||
} |
||||
|
||||
realRecord = '_'+QByteArray((char*)dns_string, dns_string_len)+"._tox."+server.name; |
||||
entry = fetchLastTextRecord(realRecord, silent); |
||||
if (entry.isEmpty()) |
||||
{ |
||||
qWarning() << "queryTox3: Server "<<server.name<<" returned no record, using tox1 as a fallback"; |
||||
goto fallbackOnTox1; |
||||
} |
||||
|
||||
// Check toxdns protocol version
|
||||
verx = entry.indexOf("v="); |
||||
if (verx!=-1) { |
||||
verx += 2; |
||||
int verend = entry.indexOf(';', verx); |
||||
if (verend!=-1) |
||||
{ |
||||
QString ver = entry.mid(verx, verend-verx); |
||||
if (ver != "tox3") |
||||
{ |
||||
qWarning() << "queryTox3: Server "<<server.name<<" returned a bad version ("<<ver<<"), using tox1 as a fallback"; |
||||
goto fallbackOnTox1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Get and decrypt the tox id
|
||||
idx = entry.indexOf("id="); |
||||
if (idx < 0) { |
||||
qWarning() << "queryTox3: Server "<<server.name<<" returned an empty id, using tox1 as a fallback"; |
||||
goto fallbackOnTox1; |
||||
} |
||||
|
||||
idx += 3; |
||||
id = entry.mid(idx).toUtf8(); |
||||
uint8_t toxId[TOX_FRIEND_ADDRESS_SIZE]; |
||||
toxIdSize = tox_decrypt_dns3_TXT(tox_dns3, toxId, (uint8_t*)id.data(), id.size(), request_id); |
||||
if (toxIdSize < 0) // We can always fallback on tox1 if toxdns3 fails
|
||||
{ |
||||
qWarning() << "queryTox3: failed to decrypt dns3 reply for "<<server.name<<", using tox1 as a fallback"; |
||||
goto fallbackOnTox1; |
||||
} |
||||
|
||||
tox_dns3_kill(tox_dns3); |
||||
toxIdStr = CFriendAddress::toString(toxId); |
||||
return toxIdStr; |
||||
|
||||
// Centralized error handling, fallback on tox1 queries
|
||||
fallbackOnTox1: |
||||
if (tox_dns3) |
||||
tox_dns3_kill(tox_dns3); |
||||
queryTox1(record, silent); |
||||
return toxIdStr; |
||||
} |
||||
|
||||
ToxID ToxDNS::resolveToxAddress(const QString &address, bool silent) |
||||
{ |
||||
ToxID toxId; |
||||
|
||||
if (address.isEmpty()) { |
||||
return toxId; |
||||
} else if (ToxID::isToxId(address)) { |
||||
toxId = ToxID::fromString(address); |
||||
return toxId; |
||||
} else { |
||||
// If we're querying one of our pinned server, do a tox3 request directly
|
||||
QString servname = address.mid(address.indexOf('@')+1); |
||||
for (const ToxDNS::tox3_server& pin : ToxDNS::pinnedServers) |
||||
{ |
||||
if (servname == pin.name) |
||||
{ |
||||
toxId = ToxID::fromString(queryTox3(pin, address, silent)); |
||||
return toxId; |
||||
} |
||||
} |
||||
|
||||
// Otherwise try tox3 if we can get a pubkey or fallback to tox1
|
||||
QByteArray pubkey = fetchLastTextRecord("_tox."+servname, true); |
||||
if (!pubkey.isEmpty()) |
||||
{ |
||||
QByteArray servnameData = servname.toUtf8(); |
||||
ToxDNS::tox3_server server; |
||||
server.name = servnameData.data(); |
||||
server.pubkey = (uint8_t*)pubkey.data(); |
||||
toxId = ToxID::fromString(queryTox3(server, address, silent)); |
||||
} |
||||
else |
||||
{ |
||||
toxId = ToxID::fromString(queryTox1(address, silent)); |
||||
} |
||||
return toxId; |
||||
} |
||||
} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
#ifndef QTOXDNS_H |
||||
#define QTOXDNS_H |
||||
|
||||
#include "src/corestructs.h" |
||||
#include <QDnsLookup> |
||||
#include <QObject> |
||||
|
||||
/// Handles tox1 and tox3 DNS queries
|
||||
class ToxDNS : public QObject |
||||
{ |
||||
public: |
||||
struct tox3_server ///< Represents a tox3 server
|
||||
{ |
||||
tox3_server()=default; |
||||
tox3_server(const char* _name, uint8_t _pk[32]):name{_name},pubkey{_pk}{} |
||||
|
||||
const char* name; ///< Hostname of the server, e.g. toxme.se
|
||||
uint8_t* pubkey; ///< Public key of the tox3 server, usually 256bit long
|
||||
}; |
||||
|
||||
public: |
||||
/// Tries to map a text string to a ToxID struct, will query Tox DNS records if necessary
|
||||
static ToxID resolveToxAddress(const QString& address, bool silent=true); |
||||
|
||||
static QString queryTox1(const QString& record, bool silent=true); ///< Record should look like user@domain.tld
|
||||
static QString queryTox3(const tox3_server& server, const QString& record, bool silent=true); ///< Record should look like user@domain.tld, may fallback on queryTox1
|
||||
|
||||
protected: |
||||
static void showWarning(const QString& message); |
||||
ToxDNS()=default; |
||||
|
||||
private: |
||||
/// Try to fetch the first entry of the given TXT record
|
||||
/// Returns an empty object on failure. May block for up to ~3s
|
||||
/// May display message boxes on error if silent if false
|
||||
static QByteArray fetchLastTextRecord(const QString& record, bool silent=true); |
||||
|
||||
public: |
||||
static const tox3_server pinnedServers[2]; |
||||
}; |
||||
|
||||
#endif // QTOXDNS_H
|
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
#include "src/widget/toxuri.h" |
||||
#include "src/toxdns.h" |
||||
#include "src/widget/tool/friendrequestdialog.h" |
||||
#include "src/core.h" |
||||
#include <QByteArray> |
||||
#include <QString> |
||||
#include <QMessageBox> |
||||
#include <QVBoxLayout> |
||||
#include <QDialogButtonBox> |
||||
#include <QLabel> |
||||
#include <QLineEdit> |
||||
#include <QPlainTextEdit> |
||||
#include <QPushButton> |
||||
#include <QCoreApplication> |
||||
|
||||
void toxURIEventHandler(const QByteArray& eventData) |
||||
{ |
||||
if (!eventData.startsWith("tox:")) |
||||
return; |
||||
|
||||
handleToxURI(eventData); |
||||
} |
||||
|
||||
void handleToxURI(const QString &toxURI) |
||||
{ |
||||
Core* core = Core::getInstance(); |
||||
|
||||
while (!core) |
||||
{ |
||||
core = Core::getInstance(); |
||||
qApp->processEvents(); |
||||
} |
||||
|
||||
while (!core->isReady()) |
||||
{ |
||||
qApp->processEvents(); |
||||
} |
||||
|
||||
QString toxaddr = toxURI.mid(4); |
||||
QString toxid = ToxDNS::resolveToxAddress(toxaddr, true).toString(); |
||||
|
||||
if (toxid.isEmpty()) |
||||
{ |
||||
QMessageBox::warning(0, "qTox", toxaddr+" is not a valid Tox address."); |
||||
} |
||||
else |
||||
{ |
||||
ToxURIDialog dialog(0, toxaddr, QObject::tr("Tox me maybe?","Default message in Tox URI friend requests. Write something appropriate!")); |
||||
if (dialog.exec() == QDialog::Accepted) |
||||
Core::getInstance()->requestFriendship(toxid, dialog.getRequestMessage()); |
||||
} |
||||
} |
||||
|
||||
ToxURIDialog::ToxURIDialog(QWidget *parent, const QString &userId, const QString &message) : |
||||
QDialog(parent) |
||||
{ |
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); |
||||
setWindowTitle(tr("Add a friend","Title of the window to add a friend through Tox URI")); |
||||
|
||||
QLabel *friendsLabel = new QLabel(tr("Do you want to add %1 as a friend ?").arg(userId), this); |
||||
QLabel *userIdLabel = new QLabel(tr("User ID:"), this); |
||||
QLineEdit *userIdEdit = new QLineEdit(userId, this); |
||||
userIdEdit->setCursorPosition(0); |
||||
userIdEdit->setReadOnly(true); |
||||
QLabel *messageLabel = new QLabel(tr("Friend request message:"), this); |
||||
messageEdit = new QPlainTextEdit(message, this); |
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal, this); |
||||
|
||||
buttonBox->addButton(tr("Send","Send a friend request"), QDialogButtonBox::AcceptRole); |
||||
buttonBox->addButton(tr("Cancel","Don't send a friend request"), QDialogButtonBox::RejectRole); |
||||
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &FriendRequestDialog::accept); |
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &FriendRequestDialog::reject); |
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this); |
||||
|
||||
layout->addWidget(friendsLabel); |
||||
layout->addSpacing(12); |
||||
layout->addWidget(userIdLabel); |
||||
layout->addWidget(userIdEdit); |
||||
layout->addWidget(messageLabel); |
||||
layout->addWidget(messageEdit); |
||||
layout->addWidget(buttonBox); |
||||
|
||||
resize(300, 200); |
||||
} |
||||
|
||||
QString ToxURIDialog::getRequestMessage() |
||||
{ |
||||
return messageEdit->toPlainText(); |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
#ifndef TOXURI_H |
||||
#define TOXURI_H |
||||
|
||||
#include <QDialog> |
||||
|
||||
/// 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); |
||||
|
||||
// Internals
|
||||
class QByteArray; |
||||
class QPlainTextEdit; |
||||
void toxURIEventHandler(const QByteArray& eventData); |
||||
class ToxURIDialog : public QDialog |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
explicit ToxURIDialog(QWidget *parent, const QString &userId, const QString &message); |
||||
QString getRequestMessage(); |
||||
|
||||
private: |
||||
QPlainTextEdit *messageEdit; |
||||
}; |
||||
|
||||
#endif // TOXURI_H
|
Loading…
Reference in new issue