mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
style: reformat current C++ codebase using clang-format
This commit is contained in:
parent
4367dc601d
commit
80f5de31b3
@ -27,8 +27,8 @@
|
||||
#include <QMutexLocker>
|
||||
#include <QPointer>
|
||||
#include <QThread>
|
||||
#include <QtMath>
|
||||
#include <QWaitCondition>
|
||||
#include <QtMath>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@ -56,8 +56,8 @@ public:
|
||||
static const ALchar* outDeviceNames()
|
||||
{
|
||||
return (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_FALSE)
|
||||
? alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER)
|
||||
: alcGetString(NULL, ALC_DEVICE_SPECIFIER);
|
||||
? alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER)
|
||||
: alcGetString(NULL, ALC_DEVICE_SPECIFIER);
|
||||
}
|
||||
|
||||
qreal inputGain() const
|
||||
@ -77,12 +77,12 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
qreal minInputGain;
|
||||
qreal maxInputGain;
|
||||
qreal minInputGain;
|
||||
qreal maxInputGain;
|
||||
|
||||
private:
|
||||
qreal gain;
|
||||
qreal gainFactor;
|
||||
qreal gain;
|
||||
qreal gainFactor;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -102,7 +102,8 @@ private:
|
||||
* @param s Name of the sound to get the path of.
|
||||
* @return The path of the requested sound.
|
||||
*
|
||||
* @fn void Audio::frameAvailable(const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate);
|
||||
* @fn void Audio::frameAvailable(const int16_t *pcm, size_t sample_count, uint8_t channels,
|
||||
* uint32_t sampling_rate);
|
||||
*
|
||||
* When there are input subscribers, we regularly emit captured audio frames with this signal
|
||||
* Always connect with a blocking queued connection lambda, else the behaviour is undefined
|
||||
@ -150,7 +151,7 @@ Audio::Audio()
|
||||
moveToThread(audioThread);
|
||||
|
||||
connect(&captureTimer, &QTimer::timeout, this, &Audio::doCapture);
|
||||
captureTimer.setInterval(AUDIO_FRAME_DURATION/2);
|
||||
captureTimer.setInterval(AUDIO_FRAME_DURATION / 2);
|
||||
captureTimer.setSingleShot(false);
|
||||
captureTimer.start();
|
||||
connect(&playMono16Timer, &QTimer::timeout, this, &Audio::playMono16SoundCleanup);
|
||||
@ -175,7 +176,7 @@ void Audio::checkAlError() noexcept
|
||||
qWarning("OpenAL error: %d", al_err);
|
||||
}
|
||||
|
||||
void Audio::checkAlcError(ALCdevice *device) noexcept
|
||||
void Audio::checkAlcError(ALCdevice* device) noexcept
|
||||
{
|
||||
const ALCenum alc_err = alcGetError(device);
|
||||
if (alc_err)
|
||||
@ -191,8 +192,7 @@ qreal Audio::outputVolume() const
|
||||
|
||||
ALfloat volume = 0.0;
|
||||
|
||||
if (alOutDev)
|
||||
{
|
||||
if (alOutDev) {
|
||||
alGetListenerf(AL_GAIN, &volume);
|
||||
checkAlError();
|
||||
}
|
||||
@ -302,8 +302,7 @@ void Audio::subscribeInput()
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
if (!autoInitInput())
|
||||
{
|
||||
if (!autoInitInput()) {
|
||||
qWarning("Failed to subscribe to audio input device.");
|
||||
return;
|
||||
}
|
||||
@ -325,7 +324,8 @@ void Audio::unsubscribeInput()
|
||||
return;
|
||||
|
||||
inSubscriptions--;
|
||||
qDebug() << "Unsubscribed from audio input device [" << inSubscriptions << "subscriptions left ]";
|
||||
qDebug() << "Unsubscribed from audio input device [" << inSubscriptions
|
||||
<< "subscriptions left ]";
|
||||
|
||||
if (!inSubscriptions)
|
||||
cleanupInput();
|
||||
@ -367,14 +367,11 @@ bool Audio::initInput(const QString& deviceName)
|
||||
const ALCsizei bufSize = (frameDuration * sampleRate * 4) / 1000 * chnls;
|
||||
|
||||
const QByteArray qDevName = deviceName.toUtf8();
|
||||
const ALchar* tmpDevName = qDevName.isEmpty()
|
||||
? nullptr
|
||||
: qDevName.constData();
|
||||
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
|
||||
alInDev = alcCaptureOpenDevice(tmpDevName, sampleRate, stereoFlag, bufSize);
|
||||
|
||||
// Restart the capture if necessary
|
||||
if (!alInDev)
|
||||
{
|
||||
if (!alInDev) {
|
||||
qWarning() << "Failed to initialize audio input device:" << deviceName;
|
||||
return false;
|
||||
}
|
||||
@ -402,13 +399,10 @@ bool Audio::initOutput(const QString& deviceName)
|
||||
assert(!alOutDev);
|
||||
|
||||
const QByteArray qDevName = deviceName.toUtf8();
|
||||
const ALchar* tmpDevName = qDevName.isEmpty()
|
||||
? nullptr
|
||||
: qDevName.constData();
|
||||
const ALchar* tmpDevName = qDevName.isEmpty() ? nullptr : qDevName.constData();
|
||||
alOutDev = alcOpenDevice(tmpDevName);
|
||||
|
||||
if (!alOutDev)
|
||||
{
|
||||
if (!alOutDev) {
|
||||
qWarning() << "Cannot open output audio device" << deviceName;
|
||||
return false;
|
||||
}
|
||||
@ -417,8 +411,7 @@ bool Audio::initOutput(const QString& deviceName)
|
||||
alOutContext = alcCreateContext(alOutDev, nullptr);
|
||||
checkAlcError(alOutDev);
|
||||
|
||||
if (!alcMakeContextCurrent(alOutContext))
|
||||
{
|
||||
if (!alcMakeContextCurrent(alOutContext)) {
|
||||
qWarning() << "Cannot create output audio context";
|
||||
return false;
|
||||
}
|
||||
@ -431,8 +424,7 @@ bool Audio::initOutput(const QString& deviceName)
|
||||
checkAlError();
|
||||
|
||||
Core* core = Core::getInstance();
|
||||
if (core)
|
||||
{
|
||||
if (core) {
|
||||
// reset each call's audio source
|
||||
core->getAv()->invalidateCallSources();
|
||||
}
|
||||
@ -466,8 +458,7 @@ void Audio::playMono16Sound(const QByteArray& data)
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(alMainSource, AL_SOURCE_STATE, &state);
|
||||
if (state == AL_PLAYING)
|
||||
{
|
||||
if (state == AL_PLAYING) {
|
||||
alSourceStop(alMainSource);
|
||||
alSourcei(alMainSource, AL_BUFFER, AL_NONE);
|
||||
}
|
||||
@ -480,7 +471,8 @@ void Audio::playMono16Sound(const QByteArray& data)
|
||||
playMono16Timer.start(durationMs + 50);
|
||||
}
|
||||
|
||||
void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate)
|
||||
void Audio::playAudioBuffer(ALuint alSource, const int16_t* data, int samples, unsigned channels,
|
||||
int sampleRate)
|
||||
{
|
||||
assert(channels == 1 || channels == 2);
|
||||
QMutexLocker locker(&audioLock);
|
||||
@ -494,24 +486,19 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
|
||||
alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued);
|
||||
alSourcei(alSource, AL_LOOPING, AL_FALSE);
|
||||
|
||||
if (processed)
|
||||
{
|
||||
if (processed) {
|
||||
ALuint bufids[processed];
|
||||
alSourceUnqueueBuffers(alSource, processed, bufids);
|
||||
alDeleteBuffers(processed - 1, bufids + 1);
|
||||
bufid = bufids[0];
|
||||
}
|
||||
else if (queued < 16)
|
||||
{
|
||||
} else if (queued < 16) {
|
||||
alGenBuffers(1, &bufid);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
alBufferData(bufid, (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, data,
|
||||
samples * 2 * channels, sampleRate);
|
||||
samples * 2 * channels, sampleRate);
|
||||
alSourceQueueBuffers(alSource, 1, &bufid);
|
||||
|
||||
ALint state;
|
||||
@ -543,14 +530,12 @@ void Audio::cleanupOutput()
|
||||
{
|
||||
outputInitialized = false;
|
||||
|
||||
if (alOutDev)
|
||||
{
|
||||
if (alOutDev) {
|
||||
alSourcei(alMainSource, AL_LOOPING, AL_FALSE);
|
||||
alSourceStop(alMainSource);
|
||||
alDeleteSources(1, &alMainSource);
|
||||
|
||||
if (alMainBuffer)
|
||||
{
|
||||
if (alMainBuffer) {
|
||||
alDeleteBuffers(1, &alMainBuffer);
|
||||
alMainBuffer = 0;
|
||||
}
|
||||
@ -578,8 +563,7 @@ void Audio::playMono16SoundCleanup()
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(alMainSource, AL_SOURCE_STATE, &state);
|
||||
if (state == AL_STOPPED)
|
||||
{
|
||||
if (state == AL_STOPPED) {
|
||||
alSourcei(alMainSource, AL_BUFFER, AL_NONE);
|
||||
alDeleteBuffers(1, &alMainBuffer);
|
||||
alMainBuffer = 0;
|
||||
@ -604,12 +588,11 @@ void Audio::doCapture()
|
||||
int16_t buf[AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS];
|
||||
alcCaptureSamples(alInDev, buf, AUDIO_FRAME_SAMPLE_COUNT);
|
||||
|
||||
for (quint32 i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS; ++i)
|
||||
{
|
||||
for (quint32 i = 0; i < AUDIO_FRAME_SAMPLE_COUNT * AUDIO_CHANNELS; ++i) {
|
||||
// gain amplification with clipping to 16-bit boundaries
|
||||
int ampPCM = qBound<int>(std::numeric_limits<int16_t>::min(),
|
||||
qRound(buf[i] * d->inputGainFactor()),
|
||||
std::numeric_limits<int16_t>::max());
|
||||
int ampPCM =
|
||||
qBound<int>(std::numeric_limits<int16_t>::min(), qRound(buf[i] * d->inputGainFactor()),
|
||||
std::numeric_limits<int16_t>::max());
|
||||
|
||||
buf[i] = static_cast<int16_t>(ampPCM);
|
||||
}
|
||||
@ -631,13 +614,11 @@ QStringList Audio::outDeviceNames()
|
||||
QStringList list;
|
||||
const ALchar* pDeviceList = Private::outDeviceNames();
|
||||
|
||||
if (pDeviceList)
|
||||
{
|
||||
while (*pDeviceList)
|
||||
{
|
||||
if (pDeviceList) {
|
||||
while (*pDeviceList) {
|
||||
int len = static_cast<int>(strlen(pDeviceList));
|
||||
list << QString::fromUtf8(pDeviceList, len);
|
||||
pDeviceList += len+1;
|
||||
pDeviceList += len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -649,13 +630,11 @@ QStringList Audio::inDeviceNames()
|
||||
QStringList list;
|
||||
const ALchar* pDeviceList = Private::inDeviceNames();
|
||||
|
||||
if (pDeviceList)
|
||||
{
|
||||
while (*pDeviceList)
|
||||
{
|
||||
if (pDeviceList) {
|
||||
while (*pDeviceList) {
|
||||
int len = static_cast<int>(strlen(pDeviceList));
|
||||
list << QString::fromUtf8(pDeviceList, len);
|
||||
pDeviceList += len+1;
|
||||
pDeviceList += len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -666,14 +645,12 @@ void Audio::subscribeOutput(ALuint& sid)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
if (!autoInitOutput())
|
||||
{
|
||||
if (!autoInitOutput()) {
|
||||
qWarning("Failed to subscribe to audio output device.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alcMakeContextCurrent(alOutContext))
|
||||
{
|
||||
if (!alcMakeContextCurrent(alOutContext)) {
|
||||
qWarning("Failed to activate output context.");
|
||||
return;
|
||||
}
|
||||
@ -682,23 +659,19 @@ void Audio::subscribeOutput(ALuint& sid)
|
||||
assert(sid);
|
||||
outSources << sid;
|
||||
|
||||
qDebug() << "Audio source" << sid << "created. Sources active:"
|
||||
<< outSources.size();
|
||||
qDebug() << "Audio source" << sid << "created. Sources active:" << outSources.size();
|
||||
}
|
||||
|
||||
void Audio::unsubscribeOutput(ALuint &sid)
|
||||
void Audio::unsubscribeOutput(ALuint& sid)
|
||||
{
|
||||
QMutexLocker locker(&audioLock);
|
||||
|
||||
outSources.removeAll(sid);
|
||||
|
||||
if (sid)
|
||||
{
|
||||
if (alIsSource(sid))
|
||||
{
|
||||
if (sid) {
|
||||
if (alIsSource(sid)) {
|
||||
alDeleteSources(1, &sid);
|
||||
qDebug() << "Audio source" << sid << "deleted. Sources active:"
|
||||
<< outSources.size();
|
||||
qDebug() << "Audio source" << sid << "deleted. Sources active:" << outSources.size();
|
||||
} else {
|
||||
qWarning() << "Trying to delete invalid audio source" << sid;
|
||||
}
|
||||
@ -724,8 +697,7 @@ void Audio::stopLoop()
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(alMainSource, AL_SOURCE_STATE, &state);
|
||||
if (state == AL_STOPPED)
|
||||
{
|
||||
if (state == AL_STOPPED) {
|
||||
alSourcei(alMainSource, AL_BUFFER, AL_NONE);
|
||||
alDeleteBuffers(1, &alMainBuffer);
|
||||
alMainBuffer = 0;
|
||||
|
@ -24,18 +24,18 @@
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#include <OpenAL/al.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#else
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
|
||||
@ -51,12 +51,16 @@ class Audio : public QObject
|
||||
class Private;
|
||||
|
||||
public:
|
||||
enum class Sound
|
||||
{
|
||||
NewMessage,
|
||||
Test,
|
||||
IncomingCall
|
||||
};
|
||||
|
||||
enum class Sound { NewMessage, Test, IncomingCall };
|
||||
|
||||
inline static QString getSound(Sound s) {
|
||||
switch (s)
|
||||
{
|
||||
inline static QString getSound(Sound s)
|
||||
{
|
||||
switch (s) {
|
||||
case Sound::Test:
|
||||
return QStringLiteral(":/audio/notification.pcm");
|
||||
case Sound::NewMessage:
|
||||
@ -100,25 +104,26 @@ public:
|
||||
void playMono16Sound(const QByteArray& data);
|
||||
void playMono16Sound(const QString& path);
|
||||
|
||||
void playAudioBuffer(ALuint alSource, const int16_t *data, int samples,
|
||||
unsigned channels, int sampleRate);
|
||||
void playAudioBuffer(ALuint alSource, const int16_t* data, int samples, unsigned channels,
|
||||
int sampleRate);
|
||||
|
||||
public:
|
||||
// Public default audio settings
|
||||
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000;
|
||||
static constexpr uint32_t AUDIO_FRAME_DURATION = 20;
|
||||
static constexpr ALint AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE/1000;
|
||||
static constexpr ALint AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE / 1000;
|
||||
static constexpr uint32_t AUDIO_CHANNELS = 2;
|
||||
|
||||
signals:
|
||||
void frameAvailable(const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate);
|
||||
void frameAvailable(const int16_t* pcm, size_t sample_count, uint8_t channels,
|
||||
uint32_t sampling_rate);
|
||||
|
||||
private:
|
||||
Audio();
|
||||
~Audio();
|
||||
|
||||
static void checkAlError() noexcept;
|
||||
static void checkAlcError(ALCdevice *device) noexcept;
|
||||
static void checkAlcError(ALCdevice* device) noexcept;
|
||||
|
||||
bool autoInitInput();
|
||||
bool autoInitOutput();
|
||||
@ -133,20 +138,20 @@ private:
|
||||
Private* d;
|
||||
|
||||
private:
|
||||
QThread* audioThread;
|
||||
mutable QMutex audioLock;
|
||||
QThread* audioThread;
|
||||
mutable QMutex audioLock;
|
||||
|
||||
ALCdevice* alInDev;
|
||||
quint32 inSubscriptions;
|
||||
QTimer captureTimer, playMono16Timer;
|
||||
ALCdevice* alInDev;
|
||||
quint32 inSubscriptions;
|
||||
QTimer captureTimer, playMono16Timer;
|
||||
|
||||
ALCdevice* alOutDev;
|
||||
ALCcontext* alOutContext;
|
||||
ALuint alMainSource;
|
||||
ALuint alMainBuffer;
|
||||
bool outputInitialized;
|
||||
ALCdevice* alOutDev;
|
||||
ALCcontext* alOutContext;
|
||||
ALuint alMainSource;
|
||||
ALuint alMainBuffer;
|
||||
bool outputInitialized;
|
||||
|
||||
QList<ALuint> outSources;
|
||||
QList<ALuint> outSources;
|
||||
};
|
||||
|
||||
#endif // AUDIO_H
|
||||
|
@ -25,13 +25,11 @@
|
||||
|
||||
ChatLine::ChatLine()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ChatLine::~ChatLine()
|
||||
{
|
||||
for (ChatLineContent* c : content)
|
||||
{
|
||||
for (ChatLineContent* c : content) {
|
||||
if (c->scene())
|
||||
c->scene()->removeItem(c);
|
||||
|
||||
@ -49,8 +47,7 @@ void ChatLine::setRow(int idx)
|
||||
|
||||
void ChatLine::visibilityChanged(bool visible)
|
||||
{
|
||||
if (isVisible != visible)
|
||||
{
|
||||
if (isVisible != visible) {
|
||||
for (ChatLineContent* c : content)
|
||||
c->visibilityChanged(visible);
|
||||
}
|
||||
@ -63,7 +60,7 @@ int ChatLine::getRow() const
|
||||
return row;
|
||||
}
|
||||
|
||||
ChatLineContent *ChatLine::getContent(int col) const
|
||||
ChatLineContent* ChatLine::getContent(int col) const
|
||||
{
|
||||
if (col < static_cast<int>(content.size()) && col >= 0)
|
||||
return content[col];
|
||||
@ -71,10 +68,9 @@ ChatLineContent *ChatLine::getContent(int col) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ChatLineContent *ChatLine::getContent(QPointF scenePos) const
|
||||
ChatLineContent* ChatLine::getContent(QPointF scenePos) const
|
||||
{
|
||||
for (ChatLineContent* c: content)
|
||||
{
|
||||
for (ChatLineContent* c : content) {
|
||||
if (c->sceneBoundingRect().contains(scenePos))
|
||||
return c;
|
||||
}
|
||||
@ -84,14 +80,13 @@ ChatLineContent *ChatLine::getContent(QPointF scenePos) const
|
||||
|
||||
void ChatLine::removeFromScene()
|
||||
{
|
||||
for (ChatLineContent* c : content)
|
||||
{
|
||||
for (ChatLineContent* c : content) {
|
||||
if (c->scene())
|
||||
c->scene()->removeItem(c);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatLine::addToScene(QGraphicsScene *scene)
|
||||
void ChatLine::addToScene(QGraphicsScene* scene)
|
||||
{
|
||||
if (!scene)
|
||||
return;
|
||||
@ -135,7 +130,8 @@ void ChatLine::updateBBox()
|
||||
bbox.setWidth(width);
|
||||
|
||||
for (ChatLineContent* c : content)
|
||||
bbox.setHeight(qMax(c->sceneBoundingRect().top() - bbox.top() + c->sceneBoundingRect().height(), bbox.height()));
|
||||
bbox.setHeight(qMax(c->sceneBoundingRect().top() - bbox.top() + c->sceneBoundingRect().height(),
|
||||
bbox.height()));
|
||||
}
|
||||
|
||||
QRectF ChatLine::sceneBoundingRect() const
|
||||
@ -152,10 +148,9 @@ void ChatLine::addColumn(ChatLineContent* item, ColumnFormat fmt)
|
||||
content.push_back(item);
|
||||
}
|
||||
|
||||
void ChatLine::replaceContent(int col, ChatLineContent *lineContent)
|
||||
void ChatLine::replaceContent(int col, ChatLineContent* lineContent)
|
||||
{
|
||||
if (col >= 0 && col < static_cast<int>(content.size()) && lineContent)
|
||||
{
|
||||
if (col >= 0 && col < static_cast<int>(content.size()) && lineContent) {
|
||||
QGraphicsScene* scene = content[col]->scene();
|
||||
delete content[col];
|
||||
|
||||
@ -179,11 +174,10 @@ void ChatLine::layout(qreal w, QPointF scenePos)
|
||||
width = w;
|
||||
bbox.setTopLeft(scenePos);
|
||||
|
||||
qreal fixedWidth = (content.size()-1) * columnSpacing;
|
||||
qreal fixedWidth = (content.size() - 1) * columnSpacing;
|
||||
qreal varWidth = 0.0; // used for normalisation
|
||||
|
||||
for (int i = 0; i < format.size(); ++i)
|
||||
{
|
||||
for (int i = 0; i < format.size(); ++i) {
|
||||
if (format[i].policy == ColumnFormat::FixedSize)
|
||||
fixedWidth += format[i].size;
|
||||
else
|
||||
@ -199,8 +193,7 @@ void ChatLine::layout(qreal w, QPointF scenePos)
|
||||
qreal xOffset = 0.0;
|
||||
QVector<qreal> xPos(content.size());
|
||||
|
||||
for (int i = 0; i < content.size(); ++i)
|
||||
{
|
||||
for (int i = 0; i < content.size(); ++i) {
|
||||
// calculate the effective width of the current column
|
||||
qreal width;
|
||||
if (format[i].policy == ColumnFormat::FixedSize)
|
||||
@ -214,8 +207,7 @@ void ChatLine::layout(qreal w, QPointF scenePos)
|
||||
// calculate horizontal alignment
|
||||
qreal xAlign = 0.0;
|
||||
|
||||
switch(format[i].hAlign)
|
||||
{
|
||||
switch (format[i].hAlign) {
|
||||
case ColumnFormat::Left:
|
||||
break;
|
||||
case ColumnFormat::Right:
|
||||
@ -233,8 +225,7 @@ void ChatLine::layout(qreal w, QPointF scenePos)
|
||||
maxVOffset = qMax(maxVOffset, content[i]->getAscent());
|
||||
}
|
||||
|
||||
for (int i = 0; i < content.size(); ++i)
|
||||
{
|
||||
for (int i = 0; i < content.size(); ++i) {
|
||||
// calculate vertical alignment
|
||||
// vertical alignment may depend on width, so we do it in a second pass
|
||||
qreal yOffset = maxVOffset - content[i]->getAscent();
|
||||
|
@ -20,10 +20,10 @@
|
||||
#ifndef CHATLINE_H
|
||||
#define CHATLINE_H
|
||||
|
||||
#include <memory>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QVector>
|
||||
#include <memory>
|
||||
|
||||
class ChatLog;
|
||||
class ChatLineContent;
|
||||
@ -33,23 +33,28 @@ class QFont;
|
||||
|
||||
struct ColumnFormat
|
||||
{
|
||||
enum Policy {
|
||||
enum Policy
|
||||
{
|
||||
FixedSize,
|
||||
VariableSize,
|
||||
};
|
||||
|
||||
enum Align {
|
||||
enum Align
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
};
|
||||
|
||||
ColumnFormat() {}
|
||||
ColumnFormat()
|
||||
{
|
||||
}
|
||||
ColumnFormat(qreal s, Policy p, Align halign = Left)
|
||||
: size(s)
|
||||
, policy(p)
|
||||
, hAlign(halign)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
qreal size = 1.0;
|
||||
Policy policy = VariableSize;
|
||||
@ -86,7 +91,7 @@ public:
|
||||
|
||||
bool isOverSelection(QPointF scenePos);
|
||||
|
||||
//comparators
|
||||
// comparators
|
||||
static bool lessThanBSRectTop(const ChatLine::Ptr& lhs, const qreal& rhs);
|
||||
static bool lessThanBSRectBottom(const ChatLine::Ptr& lhs, const qreal& rhs);
|
||||
static bool lessThanRowIndex(const ChatLine::Ptr& lhs, const ChatLine::Ptr& rhs);
|
||||
@ -109,7 +114,6 @@ private:
|
||||
qreal columnSpacing = 15.0;
|
||||
QRectF bbox;
|
||||
bool isVisible = false;
|
||||
|
||||
};
|
||||
|
||||
#endif // CHATLINE_H
|
||||
|
@ -42,27 +42,22 @@ int ChatLineContent::type() const
|
||||
|
||||
void ChatLineContent::selectionMouseMove(QPointF)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ChatLineContent::selectionStarted(QPointF)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ChatLineContent::selectionCleared()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ChatLineContent::selectionDoubleClick(QPointF)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ChatLineContent::selectionFocusChanged(bool)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ChatLineContent::isOverSelection(QPointF) const
|
||||
@ -86,7 +81,6 @@ qreal ChatLineContent::getAscent() const
|
||||
|
||||
void ChatLineContent::visibilityChanged(bool)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString ChatLineContent::getText() const
|
||||
|
@ -19,10 +19,10 @@
|
||||
|
||||
#include "chatlinecontentproxy.h"
|
||||
#include "src/chatlog/content/filetransferwidget.h"
|
||||
#include <QLayout>
|
||||
#include <QWidget>
|
||||
#include <QPainter>
|
||||
#include <QDebug>
|
||||
#include <QLayout>
|
||||
#include <QPainter>
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* @enum ChatLineContentProxy::ChatLineContentProxyType
|
||||
@ -32,7 +32,8 @@
|
||||
* @value FileTransferWidgetType = 0
|
||||
*/
|
||||
|
||||
ChatLineContentProxy::ChatLineContentProxy(QWidget* widget, ChatLineContentProxyType type, int minWidth, float widthInPercent)
|
||||
ChatLineContentProxy::ChatLineContentProxy(QWidget* widget, ChatLineContentProxyType type,
|
||||
int minWidth, float widthInPercent)
|
||||
: widthPercent(widthInPercent)
|
||||
, widthMin(minWidth)
|
||||
, widgetType{type}
|
||||
@ -58,7 +59,8 @@ QRectF ChatLineContentProxy::boundingRect() const
|
||||
return result;
|
||||
}
|
||||
|
||||
void ChatLineContentProxy::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
|
||||
void ChatLineContentProxy::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
|
||||
QWidget* widget)
|
||||
{
|
||||
painter->setClipRect(boundingRect());
|
||||
proxy->paint(painter, option, widget);
|
||||
@ -76,7 +78,7 @@ QWidget* ChatLineContentProxy::getWidget() const
|
||||
|
||||
void ChatLineContentProxy::setWidth(qreal width)
|
||||
{
|
||||
proxy->widget()->setFixedWidth(qMax(static_cast<int>(width*widthPercent), widthMin));
|
||||
proxy->widget()->setFixedWidth(qMax(static_cast<int>(width * widthPercent), widthMin));
|
||||
}
|
||||
|
||||
ChatLineContentProxy::ChatLineContentProxyType ChatLineContentProxy::getWidgetType() const
|
||||
|
@ -20,8 +20,8 @@
|
||||
#ifndef CHATLINECONTENTPROXY_H
|
||||
#define CHATLINECONTENTPROXY_H
|
||||
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include "chatlinecontent.h"
|
||||
#include <QGraphicsProxyWidget>
|
||||
|
||||
class FileTransferWidget;
|
||||
|
||||
@ -33,7 +33,7 @@ public:
|
||||
enum ChatLineContentProxyType
|
||||
{
|
||||
GenericType,
|
||||
FileTransferWidgetType=0,
|
||||
FileTransferWidgetType = 0,
|
||||
};
|
||||
|
||||
public:
|
||||
@ -49,7 +49,8 @@ public:
|
||||
ChatLineContentProxyType getWidgetType() const;
|
||||
|
||||
protected:
|
||||
ChatLineContentProxy(QWidget* widget, ChatLineContentProxyType type, int minWidth, float widthInPercent);
|
||||
ChatLineContentProxy(QWidget* widget, ChatLineContentProxyType type, int minWidth,
|
||||
float widthInPercent);
|
||||
|
||||
private:
|
||||
QGraphicsProxyWidget* proxy;
|
||||
|
@ -18,27 +18,27 @@
|
||||
*/
|
||||
|
||||
#include "chatlog.h"
|
||||
#include "chatmessage.h"
|
||||
#include "chatlinecontent.h"
|
||||
#include "chatlinecontentproxy.h"
|
||||
#include "chatmessage.h"
|
||||
#include "content/filetransferwidget.h"
|
||||
#include "src/widget/translator.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QScrollBar>
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QAction>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
#include <QMouseEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QShortcut>
|
||||
#include <QTimer>
|
||||
|
||||
/**
|
||||
* @var ChatLog::repNameAfter
|
||||
* @brief repetition interval sender name (sec)
|
||||
*/
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
T clamp(T x, T min, T max)
|
||||
{
|
||||
if (x > max)
|
||||
@ -68,7 +68,7 @@ ChatLog::ChatLog(QWidget* parent)
|
||||
setBackgroundBrush(QBrush(Qt::white, Qt::SolidPattern));
|
||||
|
||||
// The selection rect for multi-line selection
|
||||
selGraphItem = scene->addRect(0,0,0,0,selectionRectColor.darker(120),selectionRectColor);
|
||||
selGraphItem = scene->addRect(0, 0, 0, 0, selectionRectColor.darker(120), selectionRectColor);
|
||||
selGraphItem->setZValue(-1.0); // behind all other items
|
||||
|
||||
// copy action (ie. Ctrl+C)
|
||||
@ -76,33 +76,24 @@ ChatLog::ChatLog(QWidget* parent)
|
||||
copyAction->setIcon(QIcon::fromTheme("edit-copy"));
|
||||
copyAction->setShortcut(QKeySequence::Copy);
|
||||
copyAction->setEnabled(false);
|
||||
connect(copyAction, &QAction::triggered, this, [this]()
|
||||
{
|
||||
copySelectedText();
|
||||
});
|
||||
connect(copyAction, &QAction::triggered, this, [this]() { copySelectedText(); });
|
||||
addAction(copyAction);
|
||||
|
||||
// Ctrl+Insert shortcut
|
||||
QShortcut* copyCtrlInsShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Insert), this);
|
||||
connect(copyCtrlInsShortcut, &QShortcut::activated, this, [this]()
|
||||
{
|
||||
copySelectedText();
|
||||
});
|
||||
connect(copyCtrlInsShortcut, &QShortcut::activated, this, [this]() { copySelectedText(); });
|
||||
|
||||
// select all action (ie. Ctrl+A)
|
||||
selectAllAction = new QAction(this);
|
||||
selectAllAction->setIcon(QIcon::fromTheme("edit-select-all"));
|
||||
selectAllAction->setShortcut(QKeySequence::SelectAll);
|
||||
connect(selectAllAction, &QAction::triggered, this, [this]()
|
||||
{
|
||||
selectAll();
|
||||
});
|
||||
connect(selectAllAction, &QAction::triggered, this, [this]() { selectAll(); });
|
||||
addAction(selectAllAction);
|
||||
|
||||
// This timer is used to scroll the view while the user is
|
||||
// moving the mouse past the top/bottom edge of the widget while selecting.
|
||||
selectionTimer = new QTimer(this);
|
||||
selectionTimer->setInterval(1000/30);
|
||||
selectionTimer->setInterval(1000 / 30);
|
||||
selectionTimer->setSingleShot(false);
|
||||
selectionTimer->start();
|
||||
connect(selectionTimer, &QTimer::timeout, this, &ChatLog::onSelectionTimerTimeout);
|
||||
@ -144,7 +135,7 @@ void ChatLog::clearSelection()
|
||||
if (selectionMode == None)
|
||||
return;
|
||||
|
||||
for (int i=selFirstRow; i<=selLastRow; ++i)
|
||||
for (int i = selFirstRow; i <= selLastRow; ++i)
|
||||
lines[i]->selectionCleared();
|
||||
|
||||
selFirstRow = -1;
|
||||
@ -183,8 +174,7 @@ void ChatLog::layout(int start, int end, qreal width)
|
||||
start = clamp<int>(start, 0, lines.size());
|
||||
end = clamp<int>(end + 1, 0, lines.size());
|
||||
|
||||
for (int i = start; i < end; ++i)
|
||||
{
|
||||
for (int i = start; i < end; ++i) {
|
||||
ChatLine* l = lines[i].get();
|
||||
|
||||
l->layout(width, QPointF(0.0, h));
|
||||
@ -196,8 +186,7 @@ void ChatLog::mousePressEvent(QMouseEvent* ev)
|
||||
{
|
||||
QGraphicsView::mousePressEvent(ev);
|
||||
|
||||
if (ev->button() == Qt::LeftButton)
|
||||
{
|
||||
if (ev->button() == Qt::LeftButton) {
|
||||
clickPos = ev->pos();
|
||||
clearSelection();
|
||||
}
|
||||
@ -216,9 +205,8 @@ void ChatLog::mouseMoveEvent(QMouseEvent* ev)
|
||||
|
||||
QPointF scenePos = mapToScene(ev->pos());
|
||||
|
||||
if (ev->buttons() & Qt::LeftButton)
|
||||
{
|
||||
//autoscroll
|
||||
if (ev->buttons() & Qt::LeftButton) {
|
||||
// autoscroll
|
||||
if (ev->pos().y() < 0)
|
||||
selectionScrollDir = Up;
|
||||
else if (ev->pos().y() > height())
|
||||
@ -226,15 +214,14 @@ void ChatLog::mouseMoveEvent(QMouseEvent* ev)
|
||||
else
|
||||
selectionScrollDir = NoDirection;
|
||||
|
||||
//select
|
||||
if (selectionMode == None && (clickPos - ev->pos()).manhattanLength() > QApplication::startDragDistance())
|
||||
{
|
||||
// select
|
||||
if (selectionMode == None
|
||||
&& (clickPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) {
|
||||
QPointF sceneClickPos = mapToScene(clickPos.toPoint());
|
||||
ChatLine::Ptr line = findLineByPosY(scenePos.y());
|
||||
|
||||
ChatLineContent* content = getContentFromPos(sceneClickPos);
|
||||
if (content)
|
||||
{
|
||||
if (content) {
|
||||
selClickedRow = content->getRow();
|
||||
selClickedCol = content->getColumn();
|
||||
selFirstRow = content->getRow();
|
||||
@ -247,9 +234,7 @@ void ChatLog::mouseMoveEvent(QMouseEvent* ev)
|
||||
// ungrab mouse grabber
|
||||
if (scene->mouseGrabberItem())
|
||||
scene->mouseGrabberItem()->ungrabMouse();
|
||||
}
|
||||
else if (line.get())
|
||||
{
|
||||
} else if (line.get()) {
|
||||
selClickedRow = line->getRow();
|
||||
selFirstRow = selClickedRow;
|
||||
selLastRow = selClickedRow;
|
||||
@ -258,44 +243,34 @@ void ChatLog::mouseMoveEvent(QMouseEvent* ev)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectionMode != None)
|
||||
{
|
||||
if (selectionMode != None) {
|
||||
ChatLineContent* content = getContentFromPos(scenePos);
|
||||
ChatLine::Ptr line = findLineByPosY(scenePos.y());
|
||||
|
||||
int row;
|
||||
|
||||
if (content)
|
||||
{
|
||||
if (content) {
|
||||
row = content->getRow();
|
||||
int col = content->getColumn();
|
||||
|
||||
if (row == selClickedRow && col == selClickedCol)
|
||||
{
|
||||
if (row == selClickedRow && col == selClickedCol) {
|
||||
selectionMode = Precise;
|
||||
|
||||
content->selectionMouseMove(scenePos);
|
||||
selGraphItem->hide();
|
||||
}
|
||||
else if (col != selClickedCol)
|
||||
{
|
||||
} else if (col != selClickedCol) {
|
||||
selectionMode = Multi;
|
||||
|
||||
lines[selClickedRow]->selectionCleared();
|
||||
}
|
||||
}
|
||||
else if (line.get())
|
||||
{
|
||||
} else if (line.get()) {
|
||||
row = line->getRow();
|
||||
|
||||
if (row != selClickedRow)
|
||||
{
|
||||
if (row != selClickedRow) {
|
||||
selectionMode = Multi;
|
||||
lines[selClickedRow]->selectionCleared();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -312,15 +287,16 @@ void ChatLog::mouseMoveEvent(QMouseEvent* ev)
|
||||
}
|
||||
}
|
||||
|
||||
//Much faster than QGraphicsScene::itemAt()!
|
||||
// Much faster than QGraphicsScene::itemAt()!
|
||||
ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const
|
||||
{
|
||||
if (lines.empty())
|
||||
return nullptr;
|
||||
|
||||
auto itr = std::lower_bound(lines.cbegin(), lines.cend(), scenePos.y(), ChatLine::lessThanBSRectBottom);
|
||||
auto itr =
|
||||
std::lower_bound(lines.cbegin(), lines.cend(), scenePos.y(), ChatLine::lessThanBSRectBottom);
|
||||
|
||||
//find content
|
||||
// find content
|
||||
if (itr != lines.cend() && (*itr)->sceneBoundingRect().contains(scenePos))
|
||||
return (*itr)->getContent(scenePos);
|
||||
|
||||
@ -329,15 +305,12 @@ ChatLineContent* ChatLog::getContentFromPos(QPointF scenePos) const
|
||||
|
||||
bool ChatLog::isOverSelection(QPointF scenePos) const
|
||||
{
|
||||
if (selectionMode == Precise)
|
||||
{
|
||||
if (selectionMode == Precise) {
|
||||
ChatLineContent* content = getContentFromPos(scenePos);
|
||||
|
||||
if (content)
|
||||
return content->isOverSelection(scenePos);
|
||||
}
|
||||
else if (selectionMode == Multi)
|
||||
{
|
||||
} else if (selectionMode == Multi) {
|
||||
if (selGraphItem->rect().contains(scenePos))
|
||||
return true;
|
||||
}
|
||||
@ -358,8 +331,7 @@ void ChatLog::reposition(int start, int end, qreal deltaY)
|
||||
start = clamp<int>(start, 0, lines.size() - 1);
|
||||
end = clamp<int>(end + 1, 0, lines.size());
|
||||
|
||||
for (int i = start; i < end; ++i)
|
||||
{
|
||||
for (int i = start; i < end; ++i) {
|
||||
ChatLine* l = lines[i].get();
|
||||
l->moveBy(deltaY);
|
||||
}
|
||||
@ -372,12 +344,12 @@ void ChatLog::insertChatlineAtBottom(ChatLine::Ptr l)
|
||||
|
||||
bool stickToBtm = stickToBottom();
|
||||
|
||||
//insert
|
||||
// insert
|
||||
l->setRow(lines.size());
|
||||
l->addToScene(scene);
|
||||
lines.append(l);
|
||||
|
||||
//partial refresh
|
||||
// partial refresh
|
||||
layout(lines.last()->getRow(), lines.size(), useableWidth());
|
||||
updateSceneRect();
|
||||
|
||||
@ -410,8 +382,7 @@ void ChatLog::insertChatlineOnTop(const QList<ChatLine::Ptr>& newLines)
|
||||
|
||||
// add the new lines
|
||||
int i = 0;
|
||||
for (ChatLine::Ptr l : newLines)
|
||||
{
|
||||
for (ChatLine::Ptr l : newLines) {
|
||||
l->addToScene(scene);
|
||||
l->visibilityChanged(false);
|
||||
l->setRow(i++);
|
||||
@ -419,8 +390,7 @@ void ChatLog::insertChatlineOnTop(const QList<ChatLine::Ptr>& newLines)
|
||||
}
|
||||
|
||||
// add the old lines
|
||||
for (ChatLine::Ptr l : lines)
|
||||
{
|
||||
for (ChatLine::Ptr l : lines) {
|
||||
l->setRow(i++);
|
||||
combLines.push_back(l);
|
||||
}
|
||||
@ -450,8 +420,7 @@ void ChatLog::startResizeWorker()
|
||||
return;
|
||||
|
||||
// (re)start the worker
|
||||
if (!workerTimer->isActive())
|
||||
{
|
||||
if (!workerTimer->isActive()) {
|
||||
// these values must not be reevaluated while the worker is running
|
||||
workerStb = stickToBottom();
|
||||
|
||||
@ -462,8 +431,7 @@ void ChatLog::startResizeWorker()
|
||||
// switch to busy scene displaying the busy notification if there is a lot
|
||||
// of text to be resized
|
||||
int txt = 0;
|
||||
for (ChatLine::Ptr line : lines)
|
||||
{
|
||||
for (ChatLine::Ptr line : lines) {
|
||||
if (txt > 500000)
|
||||
break;
|
||||
for (ChatLineContent* content : line->content)
|
||||
@ -478,13 +446,12 @@ void ChatLog::startResizeWorker()
|
||||
verticalScrollBar()->hide();
|
||||
}
|
||||
|
||||
void ChatLog::mouseDoubleClickEvent(QMouseEvent *ev)
|
||||
void ChatLog::mouseDoubleClickEvent(QMouseEvent* ev)
|
||||
{
|
||||
QPointF scenePos = mapToScene(ev->pos());
|
||||
ChatLineContent* content = getContentFromPos(scenePos);
|
||||
|
||||
if (content)
|
||||
{
|
||||
if (content) {
|
||||
content->selectionDoubleClick(scenePos);
|
||||
selClickedCol = content->getColumn();
|
||||
selClickedRow = content->getRow();
|
||||
@ -498,25 +465,24 @@ void ChatLog::mouseDoubleClickEvent(QMouseEvent *ev)
|
||||
|
||||
QString ChatLog::getSelectedText() const
|
||||
{
|
||||
if (selectionMode == Precise)
|
||||
{
|
||||
if (selectionMode == Precise) {
|
||||
return lines[selClickedRow]->content[selClickedCol]->getSelectedText();
|
||||
}
|
||||
else if (selectionMode == Multi)
|
||||
{
|
||||
} else if (selectionMode == Multi) {
|
||||
// build a nicely formatted message
|
||||
QString out;
|
||||
|
||||
for (int i=selFirstRow; i<=selLastRow; ++i)
|
||||
{
|
||||
for (int i = selFirstRow; i <= selLastRow; ++i) {
|
||||
if (lines[i]->content[1]->getText().isEmpty())
|
||||
continue;
|
||||
|
||||
QString timestamp = lines[i]->content[2]->getText().isEmpty() ? tr("pending") : lines[i]->content[2]->getText();
|
||||
QString timestamp = lines[i]->content[2]->getText().isEmpty()
|
||||
? tr("pending")
|
||||
: lines[i]->content[2]->getText();
|
||||
QString author = lines[i]->content[0]->getText();
|
||||
QString msg = lines[i]->content[1]->getText();
|
||||
|
||||
out += QString(out.isEmpty() ? "[%2] %1: %3" : "\n[%2] %1: %3").arg(author, timestamp, msg);
|
||||
out +=
|
||||
QString(out.isEmpty() ? "[%2] %1: %3" : "\n[%2] %1: %3").arg(author, timestamp, msg);
|
||||
}
|
||||
|
||||
return out;
|
||||
@ -547,8 +513,7 @@ QVector<ChatLine::Ptr> ChatLog::getLines()
|
||||
|
||||
ChatLine::Ptr ChatLog::getLatestLine() const
|
||||
{
|
||||
if (!lines.empty())
|
||||
{
|
||||
if (!lines.empty()) {
|
||||
return lines.last();
|
||||
}
|
||||
return nullptr;
|
||||
@ -559,7 +524,7 @@ ChatLine::Ptr ChatLog::getLatestLine() const
|
||||
* @param pos Position on screen in global coordinates
|
||||
* @sa getContentFromPos()
|
||||
*/
|
||||
ChatLineContent *ChatLog::getContentFromGlobalPos(QPoint pos) const
|
||||
ChatLineContent* ChatLog::getContentFromGlobalPos(QPoint pos) const
|
||||
{
|
||||
return getContentFromPos(mapToScene(mapFromGlobal(pos)));
|
||||
}
|
||||
@ -570,8 +535,7 @@ void ChatLog::clear()
|
||||
|
||||
QVector<ChatLine::Ptr> savedLines;
|
||||
|
||||
for (ChatLine::Ptr l : lines)
|
||||
{
|
||||
for (ChatLine::Ptr l : lines) {
|
||||
if (isActiveFileTransfer(l))
|
||||
savedLines.push_back(l);
|
||||
else
|
||||
@ -616,8 +580,7 @@ void ChatLog::setTypingNotification(ChatLine::Ptr notification)
|
||||
|
||||
void ChatLog::setTypingNotificationVisible(bool visible)
|
||||
{
|
||||
if (typingNotification.get())
|
||||
{
|
||||
if (typingNotification.get()) {
|
||||
typingNotification->setVisible(visible);
|
||||
updateTypingNotification();
|
||||
}
|
||||
@ -641,7 +604,7 @@ void ChatLog::selectAll()
|
||||
|
||||
selectionMode = Multi;
|
||||
selFirstRow = 0;
|
||||
selLastRow = lines.size()-1;
|
||||
selLastRow = lines.size() - 1;
|
||||
|
||||
emit selectionChanged();
|
||||
updateMultiSelectionRect();
|
||||
@ -649,8 +612,7 @@ void ChatLog::selectAll()
|
||||
|
||||
void ChatLog::fontChanged(const QFont& font)
|
||||
{
|
||||
for (ChatLine::Ptr l : lines)
|
||||
{
|
||||
for (ChatLine::Ptr l : lines) {
|
||||
l->fontChanged(font);
|
||||
}
|
||||
}
|
||||
@ -666,15 +628,16 @@ void ChatLog::checkVisibility()
|
||||
return;
|
||||
|
||||
// find first visible line
|
||||
auto lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(), ChatLine::lessThanBSRectBottom);
|
||||
auto lowerBound = std::lower_bound(lines.cbegin(), lines.cend(), getVisibleRect().top(),
|
||||
ChatLine::lessThanBSRectBottom);
|
||||
|
||||
// find last visible line
|
||||
auto upperBound = std::lower_bound(lowerBound, lines.cend(), getVisibleRect().bottom(), ChatLine::lessThanBSRectTop);
|
||||
auto upperBound = std::lower_bound(lowerBound, lines.cend(), getVisibleRect().bottom(),
|
||||
ChatLine::lessThanBSRectTop);
|
||||
|
||||
// set visibilty
|
||||
QList<ChatLine::Ptr> newVisibleLines;
|
||||
for (auto itr = lowerBound; itr != upperBound; ++itr)
|
||||
{
|
||||
for (auto itr = lowerBound; itr != upperBound; ++itr) {
|
||||
newVisibleLines.append(*itr);
|
||||
|
||||
if (!visibleLines.contains(*itr))
|
||||
@ -692,8 +655,9 @@ void ChatLog::checkVisibility()
|
||||
// enforce order
|
||||
std::sort(visibleLines.begin(), visibleLines.end(), ChatLine::lessThanRowIndex);
|
||||
|
||||
//if (!visibleLines.empty())
|
||||
// qDebug() << "visible from " << visibleLines.first()->getRow() << "to " << visibleLines.last()->getRow() << " total " << visibleLines.size();
|
||||
// if (!visibleLines.empty())
|
||||
// qDebug() << "visible from " << visibleLines.first()->getRow() << "to " <<
|
||||
// visibleLines.last()->getRow() << " total " << visibleLines.size();
|
||||
}
|
||||
|
||||
void ChatLog::scrollContentsBy(int dx, int dy)
|
||||
@ -706,8 +670,7 @@ void ChatLog::resizeEvent(QResizeEvent* ev)
|
||||
{
|
||||
bool stb = stickToBottom();
|
||||
|
||||
if (ev->size().width() != ev->oldSize().width())
|
||||
{
|
||||
if (ev->size().width() != ev->oldSize().width()) {
|
||||
startResizeWorker();
|
||||
stb = false; // let the resize worker handle it
|
||||
}
|
||||
@ -722,8 +685,7 @@ void ChatLog::resizeEvent(QResizeEvent* ev)
|
||||
|
||||
void ChatLog::updateMultiSelectionRect()
|
||||
{
|
||||
if (selectionMode == Multi && selFirstRow >= 0 && selLastRow >= 0)
|
||||
{
|
||||
if (selectionMode == Multi && selFirstRow >= 0 && selLastRow >= 0) {
|
||||
QRectF selBBox;
|
||||
selBBox = selBBox.united(lines[selFirstRow]->sceneBoundingRect());
|
||||
selBBox = selBBox.united(lines[selLastRow]->sceneBoundingRect());
|
||||
@ -733,9 +695,7 @@ void ChatLog::updateMultiSelectionRect()
|
||||
|
||||
selGraphItem->setRect(selBBox);
|
||||
selGraphItem->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
selGraphItem->hide();
|
||||
}
|
||||
}
|
||||
@ -756,10 +716,10 @@ void ChatLog::updateTypingNotification()
|
||||
|
||||
void ChatLog::updateBusyNotification()
|
||||
{
|
||||
if (busyNotification.get())
|
||||
{
|
||||
//repoisition the busy notification (centered)
|
||||
busyNotification->layout(useableWidth(), getVisibleRect().topLeft() + QPointF(0, getVisibleRect().height()/2.0));
|
||||
if (busyNotification.get()) {
|
||||
// repoisition the busy notification (centered)
|
||||
busyNotification->layout(useableWidth(), getVisibleRect().topLeft()
|
||||
+ QPointF(0, getVisibleRect().height() / 2.0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -780,15 +740,15 @@ QRectF ChatLog::calculateSceneRect() const
|
||||
if (typingNotification.get() != nullptr)
|
||||
bottom += typingNotification->sceneBoundingRect().height() + lineSpacing;
|
||||
|
||||
return QRectF(-margins.left(), -margins.top(), useableWidth(), bottom + margins.bottom() + margins.top());
|
||||
return QRectF(-margins.left(), -margins.top(), useableWidth(),
|
||||
bottom + margins.bottom() + margins.top());
|
||||
}
|
||||
|
||||
void ChatLog::onSelectionTimerTimeout()
|
||||
{
|
||||
const int scrollSpeed = 10;
|
||||
|
||||
switch(selectionScrollDir)
|
||||
{
|
||||
switch (selectionScrollDir) {
|
||||
case Up:
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->value() - scrollSpeed);
|
||||
break;
|
||||
@ -806,12 +766,11 @@ void ChatLog::onWorkerTimeout()
|
||||
// large values will make the UI unresponsive
|
||||
const int stepSize = 50;
|
||||
|
||||
layout(workerLastIndex, workerLastIndex+stepSize, useableWidth());
|
||||
layout(workerLastIndex, workerLastIndex + stepSize, useableWidth());
|
||||
workerLastIndex += stepSize;
|
||||
|
||||
// done?
|
||||
if (workerLastIndex >= lines.size())
|
||||
{
|
||||
if (workerLastIndex >= lines.size()) {
|
||||
workerTimer->stop();
|
||||
|
||||
// switch back to the scene containing the chat messages
|
||||
@ -848,11 +807,10 @@ void ChatLog::focusInEvent(QFocusEvent* ev)
|
||||
{
|
||||
QGraphicsView::focusInEvent(ev);
|
||||
|
||||
if (selectionMode != None)
|
||||
{
|
||||
if (selectionMode != None) {
|
||||
selGraphItem->setBrush(QBrush(selectionRectColor));
|
||||
|
||||
for (int i=selFirstRow; i<=selLastRow; ++i)
|
||||
for (int i = selFirstRow; i <= selLastRow; ++i)
|
||||
lines[i]->selectionFocusChanged(true);
|
||||
}
|
||||
}
|
||||
@ -861,11 +819,10 @@ void ChatLog::focusOutEvent(QFocusEvent* ev)
|
||||
{
|
||||
QGraphicsView::focusOutEvent(ev);
|
||||
|
||||
if (selectionMode != None)
|
||||
{
|
||||
if (selectionMode != None) {
|
||||
selGraphItem->setBrush(QBrush(selectionRectColor.lighter(120)));
|
||||
|
||||
for (int i=selFirstRow; i<=selLastRow; ++i)
|
||||
for (int i = selFirstRow; i <= selLastRow; ++i)
|
||||
lines[i]->selectionFocusChanged(false);
|
||||
}
|
||||
}
|
||||
@ -879,15 +836,14 @@ void ChatLog::retranslateUi()
|
||||
bool ChatLog::isActiveFileTransfer(ChatLine::Ptr l)
|
||||
{
|
||||
int count = l->getColumnCount();
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
ChatLineContent *content = l->getContent(i);
|
||||
ChatLineContentProxy *proxy = qobject_cast<ChatLineContentProxy*>(content);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ChatLineContent* content = l->getContent(i);
|
||||
ChatLineContentProxy* proxy = qobject_cast<ChatLineContentProxy*>(content);
|
||||
if (!proxy)
|
||||
continue;
|
||||
|
||||
QWidget* widget = proxy->getWidget();
|
||||
FileTransferWidget *transferWidget = qobject_cast<FileTransferWidget*>(widget);
|
||||
FileTransferWidget* transferWidget = qobject_cast<FileTransferWidget*>(widget);
|
||||
if (transferWidget && transferWidget->isActive())
|
||||
return true;
|
||||
}
|
||||
|
@ -20,8 +20,8 @@
|
||||
#ifndef CHATLOG_H
|
||||
#define CHATLOG_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <QDateTime>
|
||||
#include <QGraphicsView>
|
||||
#include <QMargins>
|
||||
|
||||
#include "chatline.h"
|
||||
@ -63,7 +63,7 @@ public:
|
||||
QVector<ChatLine::Ptr> getLines();
|
||||
ChatLine::Ptr getLatestLine() const;
|
||||
ChatLineContent* getContentFromGlobalPos(QPoint pos) const;
|
||||
const uint repNameAfter = 5*60;
|
||||
const uint repNameAfter = 5 * 60;
|
||||
|
||||
signals:
|
||||
void selectionChanged();
|
||||
@ -113,13 +113,15 @@ private:
|
||||
bool isActiveFileTransfer(ChatLine::Ptr l);
|
||||
|
||||
private:
|
||||
enum SelectionMode {
|
||||
enum SelectionMode
|
||||
{
|
||||
None,
|
||||
Precise,
|
||||
Multi,
|
||||
};
|
||||
|
||||
enum AutoScrollDirection {
|
||||
enum AutoScrollDirection
|
||||
{
|
||||
NoDirection,
|
||||
Up,
|
||||
Down,
|
||||
@ -135,7 +137,7 @@ private:
|
||||
ChatLine::Ptr busyNotification;
|
||||
|
||||
// selection
|
||||
int selClickedRow = -1; //These 4 are only valid while selectionMode != None
|
||||
int selClickedRow = -1; // These 4 are only valid while selectionMode != None
|
||||
int selClickedCol = -1;
|
||||
int selFirstRow = -1;
|
||||
int selLastRow = -1;
|
||||
@ -147,13 +149,13 @@ private:
|
||||
QTimer* workerTimer = nullptr;
|
||||
AutoScrollDirection selectionScrollDir = NoDirection;
|
||||
|
||||
//worker vars
|
||||
// worker vars
|
||||
int workerLastIndex = 0;
|
||||
bool workerStb = false;
|
||||
ChatLine::Ptr workerAnchorLine;
|
||||
|
||||
// layout
|
||||
QMargins margins = QMargins(10,10,10,10);
|
||||
QMargins margins = QMargins(10, 10, 10, 10);
|
||||
qreal lineSpacing = 5.0f;
|
||||
};
|
||||
|
||||
|
@ -20,12 +20,12 @@
|
||||
#include "chatmessage.h"
|
||||
#include "chatlinecontentproxy.h"
|
||||
#include "textformatter.h"
|
||||
#include "content/text.h"
|
||||
#include "content/timestamp.h"
|
||||
#include "content/spinner.h"
|
||||
#include "content/filetransferwidget.h"
|
||||
#include "content/image.h"
|
||||
#include "content/notificationicon.h"
|
||||
#include "content/spinner.h"
|
||||
#include "content/text.h"
|
||||
#include "content/timestamp.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@ -37,36 +37,35 @@
|
||||
|
||||
ChatMessage::ChatMessage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ChatMessage::Ptr ChatMessage::createChatMessage(const QString &sender, const QString &rawMessage, MessageType type, bool isMe, const QDateTime &date)
|
||||
ChatMessage::Ptr ChatMessage::createChatMessage(const QString& sender, const QString& rawMessage,
|
||||
MessageType type, bool isMe, const QDateTime& date)
|
||||
{
|
||||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||||
|
||||
QString text = rawMessage.toHtmlEscaped();
|
||||
QString senderText = sender;
|
||||
|
||||
const QColor actionColor = QColor("#1818FF"); // has to match the color in innerStyle.css (div.action)
|
||||
const QColor actionColor =
|
||||
QColor("#1818FF"); // has to match the color in innerStyle.css (div.action)
|
||||
|
||||
//smileys
|
||||
// smileys
|
||||
if (Settings::getInstance().getUseEmoticons())
|
||||
text = SmileyPack::getInstance().smileyfied(text);
|
||||
|
||||
//quotes (green text)
|
||||
// quotes (green text)
|
||||
text = detectQuotes(detectAnchors(text), type);
|
||||
|
||||
//text styling
|
||||
// text styling
|
||||
Settings::StyleType styleType = Settings::getInstance().getStylePreference();
|
||||
if (styleType != Settings::StyleType::NONE)
|
||||
{
|
||||
if (styleType != Settings::StyleType::NONE) {
|
||||
TextFormatter tf = TextFormatter(text);
|
||||
text = tf.applyStyling(styleType == Settings::StyleType::WITH_CHARS);
|
||||
}
|
||||
|
||||
|
||||
switch(type)
|
||||
{
|
||||
switch (type) {
|
||||
case NORMAL:
|
||||
text = wrapDiv(text, "msg");
|
||||
break;
|
||||
@ -86,9 +85,15 @@ ChatMessage::Ptr ChatMessage::createChatMessage(const QString &sender, const QSt
|
||||
if (isMe)
|
||||
authorFont.setBold(true);
|
||||
|
||||
msg->addColumn(new Text(senderText, authorFont, true, sender, type == ACTION ? actionColor : Qt::black), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Text(text, baseFont, false, ((type == ACTION) && isMe) ? QString("%1 %2").arg(sender, rawMessage) : rawMessage), ColumnFormat(1.0, ColumnFormat::VariableSize));
|
||||
msg->addColumn(new Spinner(":/ui/chatArea/spinner.svg", QSize(16, 16), 360.0/1.6), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Text(senderText, authorFont, true, sender,
|
||||
type == ACTION ? actionColor : Qt::black),
|
||||
ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Text(text, baseFont, false, ((type == ACTION) && isMe)
|
||||
? QString("%1 %2").arg(sender, rawMessage)
|
||||
: rawMessage),
|
||||
ColumnFormat(1.0, ColumnFormat::VariableSize));
|
||||
msg->addColumn(new Spinner(":/ui/chatArea/spinner.svg", QSize(16, 16), 360.0 / 1.6),
|
||||
ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
|
||||
if (!date.isNull())
|
||||
msg->markAsSent(date);
|
||||
@ -96,29 +101,39 @@ ChatMessage::Ptr ChatMessage::createChatMessage(const QString &sender, const QSt
|
||||
return msg;
|
||||
}
|
||||
|
||||
ChatMessage::Ptr ChatMessage::createChatInfoMessage(const QString &rawMessage, SystemMessageType type, const QDateTime &date)
|
||||
ChatMessage::Ptr ChatMessage::createChatInfoMessage(const QString& rawMessage,
|
||||
SystemMessageType type, const QDateTime& date)
|
||||
{
|
||||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||||
QString text = rawMessage.toHtmlEscaped();
|
||||
|
||||
QString img;
|
||||
switch(type)
|
||||
{
|
||||
case INFO: img = ":/ui/chatArea/info.svg"; break;
|
||||
case ERROR: img = ":/ui/chatArea/error.svg"; break;
|
||||
case TYPING: img = ":/ui/chatArea/typing.svg"; break;
|
||||
switch (type) {
|
||||
case INFO:
|
||||
img = ":/ui/chatArea/info.svg";
|
||||
break;
|
||||
case ERROR:
|
||||
img = ":/ui/chatArea/error.svg";
|
||||
break;
|
||||
case TYPING:
|
||||
img = ":/ui/chatArea/typing.svg";
|
||||
break;
|
||||
}
|
||||
|
||||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||||
|
||||
msg->addColumn(new Image(QSize(18, 18), img), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Text("<b>" + text + "</b>", baseFont, false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left));
|
||||
msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Image(QSize(18, 18), img),
|
||||
ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Text("<b>" + text + "</b>", baseFont, false, ""),
|
||||
ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left));
|
||||
msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont),
|
||||
ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
ChatMessage::Ptr ChatMessage::createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date)
|
||||
ChatMessage::Ptr ChatMessage::createFileTransferMessage(const QString& sender, ToxFile file,
|
||||
bool isMe, const QDateTime& date)
|
||||
{
|
||||
ChatMessage::Ptr msg = ChatMessage::Ptr(new ChatMessage);
|
||||
|
||||
@ -127,9 +142,12 @@ ChatMessage::Ptr ChatMessage::createFileTransferMessage(const QString& sender, T
|
||||
if (isMe)
|
||||
authorFont.setBold(true);
|
||||
|
||||
msg->addColumn(new Text(sender, authorFont, true), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new ChatLineContentProxy(new FileTransferWidget(0, file), 320, 0.6f), ColumnFormat(1.0, ColumnFormat::VariableSize));
|
||||
msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Text(sender, authorFont, true),
|
||||
ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new ChatLineContentProxy(new FileTransferWidget(0, file), 320, 0.6f),
|
||||
ColumnFormat(1.0, ColumnFormat::VariableSize));
|
||||
msg->addColumn(new Timestamp(date, Settings::getInstance().getTimestampFormat(), baseFont),
|
||||
ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
|
||||
return msg;
|
||||
}
|
||||
@ -140,14 +158,17 @@ ChatMessage::Ptr ChatMessage::createTypingNotification()
|
||||
|
||||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||||
|
||||
// Note: "[user]..." is just a placeholder. The actual text is set in ChatForm::setFriendTyping()
|
||||
// Note: "[user]..." is just a placeholder. The actual text is set in
|
||||
// ChatForm::setFriendTyping()
|
||||
//
|
||||
// FIXME: Due to circumstances, placeholder is being used in a case where
|
||||
// user received typing notifications constantly since contact came online.
|
||||
// This causes "[user]..." to be displayed in place of user nick, as long
|
||||
// as user will keep typing. Issue #1280
|
||||
msg->addColumn(new NotificationIcon(QSize(18, 18)), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Text("[user]...", baseFont, false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left));
|
||||
msg->addColumn(new NotificationIcon(QSize(18, 18)),
|
||||
ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right));
|
||||
msg->addColumn(new Text("[user]...", baseFont, false, ""),
|
||||
ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Left));
|
||||
|
||||
return msg;
|
||||
}
|
||||
@ -159,12 +180,13 @@ ChatMessage::Ptr ChatMessage::createBusyNotification()
|
||||
baseFont.setPixelSize(baseFont.pixelSize() + 2);
|
||||
baseFont.setBold(true);
|
||||
|
||||
msg->addColumn(new Text(QObject::tr("Resizing"), baseFont, false, ""), ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Center));
|
||||
msg->addColumn(new Text(QObject::tr("Resizing"), baseFont, false, ""),
|
||||
ColumnFormat(1.0, ColumnFormat::VariableSize, ColumnFormat::Center));
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
void ChatMessage::markAsSent(const QDateTime &time)
|
||||
void ChatMessage::markAsSent(const QDateTime& time)
|
||||
{
|
||||
QFont baseFont = Settings::getInstance().getChatMessageFont();
|
||||
|
||||
@ -205,30 +227,29 @@ void ChatMessage::hideDate()
|
||||
c->hide();
|
||||
}
|
||||
|
||||
QString ChatMessage::detectAnchors(const QString &str)
|
||||
QString ChatMessage::detectAnchors(const QString& str)
|
||||
{
|
||||
QString out = str;
|
||||
|
||||
// detect URIs
|
||||
QRegExp exp("("
|
||||
"(?:\\b)((www\\.)|(http[s]?|ftp)://)" // (protocol)://(printable - non-special character)
|
||||
// http://ONEORMOREALHPA-DIGIT
|
||||
"\\w+\\S+)" // any other character, lets domains and other
|
||||
// ↓ link to a file, or samba share
|
||||
// https://en.wikipedia.org/wiki/File_URI_scheme
|
||||
"|(?:\\b)((file|smb)://)([\\S| ]*)"
|
||||
"|(?:\\b)(tox:[a-zA-Z\\d]{76})" //link with full user address
|
||||
"|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)" //@mail link
|
||||
"|(?:\\b)(tox:\\S+@\\S+)"); // starts with `tox` then : and only alpha-digits till the end
|
||||
// also accepts tox:agilob@net as simplified TOX ID
|
||||
QRegExp exp(
|
||||
"("
|
||||
"(?:\\b)((www\\.)|(http[s]?|ftp)://)" // (protocol)://(printable - non-special character)
|
||||
// http://ONEORMOREALHPA-DIGIT
|
||||
"\\w+\\S+)" // any other character, lets domains and other
|
||||
// ↓ link to a file, or samba share
|
||||
// https://en.wikipedia.org/wiki/File_URI_scheme
|
||||
"|(?:\\b)((file|smb)://)([\\S| ]*)"
|
||||
"|(?:\\b)(tox:[a-zA-Z\\d]{76})" // link with full user address
|
||||
"|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)" //@mail link
|
||||
"|(?:\\b)(tox:\\S+@\\S+)"); // starts with `tox` then : and only alpha-digits till the end
|
||||
// also accepts tox:agilob@net as simplified TOX ID
|
||||
|
||||
int offset = 0;
|
||||
while ((offset = exp.indexIn(out, offset)) != -1)
|
||||
{
|
||||
while ((offset = exp.indexIn(out, offset)) != -1) {
|
||||
QString url = exp.cap();
|
||||
// If there's a trailing " it's a HTML attribute, e.g. a smiley img's title=":tox:"
|
||||
if (url == "tox:\"")
|
||||
{
|
||||
if (url == "tox:\"") {
|
||||
offset += url.length();
|
||||
continue;
|
||||
}
|
||||
@ -250,25 +271,20 @@ QString ChatMessage::detectQuotes(const QString& str, MessageType type)
|
||||
// detect text quotes
|
||||
QStringList messageLines = str.split("\n");
|
||||
QString quotedText;
|
||||
for (int i = 0; i < messageLines.size(); ++i)
|
||||
{
|
||||
for (int i = 0; i < messageLines.size(); ++i) {
|
||||
// don't quote first line in action message. This makes co-existence of
|
||||
// quotes and action messages possible, since only first line can cause
|
||||
// problems in case where there is quote in it used.
|
||||
if (QRegExp("^(>|>).*").exactMatch(messageLines[i]))
|
||||
{
|
||||
if (QRegExp("^(>|>).*").exactMatch(messageLines[i])) {
|
||||
if (i > 0 || type != ACTION)
|
||||
quotedText += "<span class=quote>" + messageLines[i] + "</span>";
|
||||
else
|
||||
quotedText += messageLines[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
quotedText += messageLines[i];
|
||||
}
|
||||
|
||||
if (i < messageLines.size() - 1)
|
||||
{
|
||||
if (i < messageLines.size() - 1) {
|
||||
quotedText += '\n';
|
||||
}
|
||||
}
|
||||
@ -276,7 +292,7 @@ QString ChatMessage::detectQuotes(const QString& str, MessageType type)
|
||||
return quotedText;
|
||||
}
|
||||
|
||||
QString ChatMessage::wrapDiv(const QString &str, const QString &div)
|
||||
QString ChatMessage::wrapDiv(const QString& str, const QString& div)
|
||||
{
|
||||
return QString("<p class=%1>%2</p>").arg(div, /*QChar(0x200E) + */QString(str));
|
||||
return QString("<p class=%1>%2</p>").arg(div, /*QChar(0x200E) + */ QString(str));
|
||||
}
|
||||
|
@ -47,9 +47,13 @@ public:
|
||||
|
||||
ChatMessage();
|
||||
|
||||
static ChatMessage::Ptr createChatMessage(const QString& sender, const QString& rawMessage, MessageType type, bool isMe, const QDateTime& date = QDateTime());
|
||||
static ChatMessage::Ptr createChatInfoMessage(const QString& rawMessage, SystemMessageType type, const QDateTime& date);
|
||||
static ChatMessage::Ptr createFileTransferMessage(const QString& sender, ToxFile file, bool isMe, const QDateTime& date);
|
||||
static ChatMessage::Ptr createChatMessage(const QString& sender, const QString& rawMessage,
|
||||
MessageType type, bool isMe,
|
||||
const QDateTime& date = QDateTime());
|
||||
static ChatMessage::Ptr createChatInfoMessage(const QString& rawMessage, SystemMessageType type,
|
||||
const QDateTime& date);
|
||||
static ChatMessage::Ptr createFileTransferMessage(const QString& sender, ToxFile file,
|
||||
bool isMe, const QDateTime& date);
|
||||
static ChatMessage::Ptr createTypingNotification();
|
||||
static ChatMessage::Ptr createBusyNotification();
|
||||
|
||||
|
@ -20,23 +20,23 @@
|
||||
#include "filetransferwidget.h"
|
||||
#include "ui_filetransferwidget.h"
|
||||
|
||||
#include "src/nexus.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/widget/gui.h"
|
||||
#include "src/widget/style.h"
|
||||
#include "src/widget/widget.h"
|
||||
#include "src/persistence/settings.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QFile>
|
||||
#include <QBuffer>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QVariantAnimation>
|
||||
#include <QDebug>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
@ -63,10 +63,11 @@ FileTransferWidget::FileTransferWidget(QWidget* parent, ToxFile file)
|
||||
backgroundColorAnimation = new QVariantAnimation(this);
|
||||
backgroundColorAnimation->setDuration(500);
|
||||
backgroundColorAnimation->setEasingCurve(QEasingCurve::OutCubic);
|
||||
connect(backgroundColorAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) {
|
||||
backgroundColor = val.value<QColor>();
|
||||
update();
|
||||
});
|
||||
connect(backgroundColorAnimation, &QVariantAnimation::valueChanged, this,
|
||||
[this](const QVariant& val) {
|
||||
backgroundColor = val.value<QColor>();
|
||||
update();
|
||||
});
|
||||
|
||||
buttonColorAnimation = new QVariantAnimation(this);
|
||||
buttonColorAnimation->setDuration(500);
|
||||
@ -78,27 +79,32 @@ FileTransferWidget::FileTransferWidget(QWidget* parent, ToxFile file)
|
||||
|
||||
setBackgroundColor(Style::getColor(Style::LightGrey), false);
|
||||
|
||||
connect(Core::getInstance(), &Core::fileTransferInfo, this, &FileTransferWidget::onFileTransferInfo);
|
||||
connect(Core::getInstance(), &Core::fileTransferAccepted, this, &FileTransferWidget::onFileTransferAccepted);
|
||||
connect(Core::getInstance(), &Core::fileTransferCancelled, this, &FileTransferWidget::onFileTransferCancelled);
|
||||
connect(Core::getInstance(), &Core::fileTransferPaused, this, &FileTransferWidget::onFileTransferPaused);
|
||||
connect(Core::getInstance(), &Core::fileTransferFinished, this, &FileTransferWidget::onFileTransferFinished);
|
||||
connect(Core::getInstance(), &Core::fileTransferRemotePausedUnpaused, this, &FileTransferWidget::fileTransferRemotePausedUnpaused);
|
||||
connect(Core::getInstance(), &Core::fileTransferBrokenUnbroken, this, &FileTransferWidget::fileTransferBrokenUnbroken);
|
||||
connect(Core::getInstance(), &Core::fileTransferInfo, this,
|
||||
&FileTransferWidget::onFileTransferInfo);
|
||||
connect(Core::getInstance(), &Core::fileTransferAccepted, this,
|
||||
&FileTransferWidget::onFileTransferAccepted);
|
||||
connect(Core::getInstance(), &Core::fileTransferCancelled, this,
|
||||
&FileTransferWidget::onFileTransferCancelled);
|
||||
connect(Core::getInstance(), &Core::fileTransferPaused, this,
|
||||
&FileTransferWidget::onFileTransferPaused);
|
||||
connect(Core::getInstance(), &Core::fileTransferFinished, this,
|
||||
&FileTransferWidget::onFileTransferFinished);
|
||||
connect(Core::getInstance(), &Core::fileTransferRemotePausedUnpaused, this,
|
||||
&FileTransferWidget::fileTransferRemotePausedUnpaused);
|
||||
connect(Core::getInstance(), &Core::fileTransferBrokenUnbroken, this,
|
||||
&FileTransferWidget::fileTransferBrokenUnbroken);
|
||||
connect(ui->topButton, &QPushButton::clicked, this, &FileTransferWidget::onTopButtonClicked);
|
||||
connect(ui->bottomButton, &QPushButton::clicked, this, &FileTransferWidget::onBottomButtonClicked);
|
||||
connect(ui->previewButton, &QPushButton::clicked, this, &FileTransferWidget::onPreviewButtonClicked);
|
||||
connect(ui->previewButton, &QPushButton::clicked, this,
|
||||
&FileTransferWidget::onPreviewButtonClicked);
|
||||
|
||||
setupButtons();
|
||||
|
||||
//preview
|
||||
if (fileInfo.direction == ToxFile::SENDING)
|
||||
{
|
||||
// preview
|
||||
if (fileInfo.direction == ToxFile::SENDING) {
|
||||
showPreview(fileInfo.filePath);
|
||||
ui->progressLabel->setText(tr("Waiting to send...", "file transfer widget"));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->progressLabel->setText(tr("Accept to receive this file", "file transfer widget"));
|
||||
}
|
||||
|
||||
@ -110,7 +116,7 @@ FileTransferWidget::~FileTransferWidget()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void FileTransferWidget::autoAcceptTransfer(const QString &path)
|
||||
void FileTransferWidget::autoAcceptTransfer(const QString& path)
|
||||
{
|
||||
QString filepath;
|
||||
int number = 0;
|
||||
@ -118,15 +124,16 @@ void FileTransferWidget::autoAcceptTransfer(const QString &path)
|
||||
QString suffix = QFileInfo(fileInfo.fileName).completeSuffix();
|
||||
QString base = QFileInfo(fileInfo.fileName).baseName();
|
||||
|
||||
do
|
||||
{
|
||||
filepath = QString("%1/%2%3.%4").arg(path, base, number > 0 ? QString(" (%1)").arg(QString::number(number)) : QString(), suffix);
|
||||
do {
|
||||
filepath = QString("%1/%2%3.%4")
|
||||
.arg(path, base,
|
||||
number > 0 ? QString(" (%1)").arg(QString::number(number)) : QString(),
|
||||
suffix);
|
||||
++number;
|
||||
}
|
||||
while (QFileInfo(filepath).exists());
|
||||
} while (QFileInfo(filepath).exists());
|
||||
|
||||
//Do not automatically accept the file-transfer if the path is not writable.
|
||||
//The user can still accept it manually.
|
||||
// Do not automatically accept the file-transfer if the path is not writable.
|
||||
// The user can still accept it manually.
|
||||
if (Nexus::tryRemoveFile(filepath))
|
||||
Core::getInstance()->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath);
|
||||
else
|
||||
@ -138,27 +145,27 @@ bool FileTransferWidget::isActive() const
|
||||
return active;
|
||||
}
|
||||
|
||||
void FileTransferWidget::acceptTransfer(const QString &filepath)
|
||||
void FileTransferWidget::acceptTransfer(const QString& filepath)
|
||||
{
|
||||
if (filepath.isEmpty())
|
||||
return;
|
||||
|
||||
//test if writable
|
||||
if (!Nexus::tryRemoveFile(filepath))
|
||||
{
|
||||
// test if writable
|
||||
if (!Nexus::tryRemoveFile(filepath)) {
|
||||
GUI::showWarning(tr("Location not writable", "Title of permissions popup"),
|
||||
tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup"));
|
||||
tr("You do not have permission to write that location. Choose another, or "
|
||||
"cancel the save dialog.",
|
||||
"text of permissions popup"));
|
||||
return;
|
||||
}
|
||||
|
||||
//everything ok!
|
||||
// everything ok!
|
||||
Core::getInstance()->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath);
|
||||
}
|
||||
|
||||
void FileTransferWidget::setBackgroundColor(const QColor &c, bool whiteFont)
|
||||
void FileTransferWidget::setBackgroundColor(const QColor& c, bool whiteFont)
|
||||
{
|
||||
if (c != backgroundColor)
|
||||
{
|
||||
if (c != backgroundColor) {
|
||||
backgroundColorAnimation->setStartValue(backgroundColor);
|
||||
backgroundColorAnimation->setEndValue(c);
|
||||
backgroundColorAnimation->start();
|
||||
@ -172,10 +179,9 @@ void FileTransferWidget::setBackgroundColor(const QColor &c, bool whiteFont)
|
||||
update();
|
||||
}
|
||||
|
||||
void FileTransferWidget::setButtonColor(const QColor &c)
|
||||
void FileTransferWidget::setButtonColor(const QColor& c)
|
||||
{
|
||||
if (c != buttonColor)
|
||||
{
|
||||
if (c != buttonColor) {
|
||||
buttonColorAnimation->setStartValue(buttonColor);
|
||||
buttonColorAnimation->setEndValue(c);
|
||||
buttonColorAnimation->start();
|
||||
@ -184,11 +190,11 @@ void FileTransferWidget::setButtonColor(const QColor &c)
|
||||
|
||||
bool FileTransferWidget::drawButtonAreaNeeded() const
|
||||
{
|
||||
return (ui->bottomButton->isVisible() || ui->topButton->isVisible()) &&
|
||||
!(ui->topButton->isVisible() && ui->topButton->objectName() == "ok");
|
||||
return (ui->bottomButton->isVisible() || ui->topButton->isVisible())
|
||||
&& !(ui->topButton->isVisible() && ui->topButton->objectName() == "ok");
|
||||
}
|
||||
|
||||
void FileTransferWidget::paintEvent(QPaintEvent *)
|
||||
void FileTransferWidget::paintEvent(QPaintEvent*)
|
||||
{
|
||||
// required by Hi-DPI support as border-image doesn't work.
|
||||
QPainter painter(this);
|
||||
@ -202,21 +208,22 @@ void FileTransferWidget::paintEvent(QPaintEvent *)
|
||||
|
||||
// draw background
|
||||
if (drawButtonAreaNeeded())
|
||||
painter.setClipRect(QRect(0, 0, width()-buttonFieldWidth, height()));
|
||||
painter.setClipRect(QRect(0, 0, width() - buttonFieldWidth, height()));
|
||||
|
||||
painter.setBrush(QBrush(backgroundColor));
|
||||
painter.drawRoundRect(geometry(), r * ratio, r);
|
||||
|
||||
if (drawButtonAreaNeeded())
|
||||
{
|
||||
if (drawButtonAreaNeeded()) {
|
||||
// draw button background (top)
|
||||
painter.setBrush(QBrush(buttonColor));
|
||||
painter.setClipRect(QRect(width()-buttonFieldWidth+lineWidth,0,buttonFieldWidth,height()/2-ceil(lineWidth/2.0)));
|
||||
painter.setClipRect(QRect(width() - buttonFieldWidth + lineWidth, 0, buttonFieldWidth,
|
||||
height() / 2 - ceil(lineWidth / 2.0)));
|
||||
painter.drawRoundRect(geometry(), r * ratio, r);
|
||||
|
||||
// draw button background (bottom)
|
||||
painter.setBrush(QBrush(buttonColor));
|
||||
painter.setClipRect(QRect(width()-buttonFieldWidth+lineWidth,height()/2+lineWidth/2,buttonFieldWidth,height()/2));
|
||||
painter.setClipRect(QRect(width() - buttonFieldWidth + lineWidth,
|
||||
height() / 2 + lineWidth / 2, buttonFieldWidth, height() / 2));
|
||||
painter.drawRoundRect(geometry(), r * ratio, r);
|
||||
}
|
||||
}
|
||||
@ -224,15 +231,14 @@ void FileTransferWidget::paintEvent(QPaintEvent *)
|
||||
void FileTransferWidget::onFileTransferInfo(ToxFile file)
|
||||
{
|
||||
QTime now = QTime::currentTime();
|
||||
qint64 dt = lastTick.msecsTo(now); //ms
|
||||
qint64 dt = lastTick.msecsTo(now); // ms
|
||||
|
||||
if (fileInfo != file || dt < 1000)
|
||||
return;
|
||||
|
||||
fileInfo = file;
|
||||
|
||||
if (fileInfo.status == ToxFile::TRANSMITTING)
|
||||
{
|
||||
if (fileInfo.status == ToxFile::TRANSMITTING) {
|
||||
// update progress
|
||||
qreal progress = static_cast<qreal>(file.bytesSent) / static_cast<qreal>(file.filesize);
|
||||
ui->progressBar->setValue(static_cast<int>(progress * 100.0));
|
||||
@ -241,9 +247,8 @@ void FileTransferWidget::onFileTransferInfo(ToxFile file)
|
||||
qreal deltaSecs = dt / 1000.0;
|
||||
|
||||
// (can't use ::abs or ::max on unsigned types substraction, they'd just overflow)
|
||||
quint64 deltaBytes = file.bytesSent > lastBytesSent
|
||||
? file.bytesSent - lastBytesSent
|
||||
: lastBytesSent - file.bytesSent;
|
||||
quint64 deltaBytes = file.bytesSent > lastBytesSent ? file.bytesSent - lastBytesSent
|
||||
: lastBytesSent - file.bytesSent;
|
||||
qreal bytesPerSec = static_cast<int>(static_cast<qreal>(deltaBytes) / deltaSecs);
|
||||
|
||||
// calculate mean
|
||||
@ -257,15 +262,12 @@ void FileTransferWidget::onFileTransferInfo(ToxFile file)
|
||||
meanBytesPerSec /= static_cast<qreal>(TRANSFER_ROLLING_AVG_COUNT);
|
||||
|
||||
// update UI
|
||||
if (meanBytesPerSec > 0)
|
||||
{
|
||||
if (meanBytesPerSec > 0) {
|
||||
// ETA
|
||||
QTime toGo = QTime(0,0).addSecs((file.filesize - file.bytesSent) / meanBytesPerSec);
|
||||
QTime toGo = QTime(0, 0).addSecs((file.filesize - file.bytesSent) / meanBytesPerSec);
|
||||
QString format = toGo.hour() > 0 ? "hh:mm:ss" : "mm:ss";
|
||||
ui->etaLabel->setText(toGo.toString(format));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->etaLabel->setText("");
|
||||
}
|
||||
|
||||
@ -320,7 +322,7 @@ void FileTransferWidget::onFileTransferPaused(ToxFile file)
|
||||
|
||||
// reset mean
|
||||
meanIndex = 0;
|
||||
for (size_t i=0; i<TRANSFER_ROLLING_AVG_COUNT; ++i)
|
||||
for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i)
|
||||
meanData[i] = 0.0;
|
||||
|
||||
setBackgroundColor(Style::getColor(Style::LightGrey), false);
|
||||
@ -340,7 +342,7 @@ void FileTransferWidget::onFileTransferResumed(ToxFile file)
|
||||
|
||||
// reset mean
|
||||
meanIndex = 0;
|
||||
for (size_t i=0; i<TRANSFER_ROLLING_AVG_COUNT; ++i)
|
||||
for (size_t i = 0; i < TRANSFER_ROLLING_AVG_COUNT; ++i)
|
||||
meanData[i] = 0.0;
|
||||
|
||||
setBackgroundColor(Style::getColor(Style::LightGrey), false);
|
||||
@ -395,11 +397,11 @@ void FileTransferWidget::fileTransferBrokenUnbroken(ToxFile file, bool broken)
|
||||
|
||||
QString FileTransferWidget::getHumanReadableSize(qint64 size)
|
||||
{
|
||||
static const char* suffix[] = {"B","kiB","MiB","GiB","TiB"};
|
||||
static const char* suffix[] = {"B", "kiB", "MiB", "GiB", "TiB"};
|
||||
int exp = 0;
|
||||
|
||||
if (size > 0)
|
||||
exp = std::min( (int) (log(size) / log(1024)), (int) (sizeof(suffix) / sizeof(suffix[0]) - 1));
|
||||
exp = std::min((int)(log(size) / log(1024)), (int)(sizeof(suffix) / sizeof(suffix[0]) - 1));
|
||||
|
||||
return QString().setNum(size / pow(1024, exp), 'f', exp > 1 ? 2 : 0).append(suffix[exp]);
|
||||
}
|
||||
@ -415,8 +417,7 @@ void FileTransferWidget::hideWidgets()
|
||||
|
||||
void FileTransferWidget::setupButtons()
|
||||
{
|
||||
switch(fileInfo.status)
|
||||
{
|
||||
switch (fileInfo.status) {
|
||||
case ToxFile::TRANSMITTING:
|
||||
ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/pause.svg"));
|
||||
ui->topButton->setObjectName("pause");
|
||||
@ -447,14 +448,11 @@ void FileTransferWidget::setupButtons()
|
||||
ui->bottomButton->setObjectName("cancel");
|
||||
ui->bottomButton->setToolTip(tr("Cancel transfer"));
|
||||
|
||||
if (fileInfo.direction == ToxFile::SENDING)
|
||||
{
|
||||
if (fileInfo.direction == ToxFile::SENDING) {
|
||||
ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/pause.svg"));
|
||||
ui->topButton->setObjectName("pause");
|
||||
ui->topButton->setToolTip(tr("Pause transfer"));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/yes.svg"));
|
||||
ui->topButton->setObjectName("accept");
|
||||
ui->topButton->setToolTip(tr("Accept transfer"));
|
||||
@ -463,18 +461,16 @@ void FileTransferWidget::setupButtons()
|
||||
}
|
||||
}
|
||||
|
||||
void FileTransferWidget::handleButton(QPushButton *btn)
|
||||
void FileTransferWidget::handleButton(QPushButton* btn)
|
||||
{
|
||||
if (fileInfo.direction == ToxFile::SENDING)
|
||||
{
|
||||
if (fileInfo.direction == ToxFile::SENDING) {
|
||||
if (btn->objectName() == "cancel")
|
||||
Core::getInstance()->cancelFileSend(fileInfo.friendId, fileInfo.fileNum);
|
||||
else if (btn->objectName() == "pause")
|
||||
Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum);
|
||||
else if (btn->objectName() == "resume")
|
||||
Core::getInstance()->pauseResumeFileSend(fileInfo.friendId, fileInfo.fileNum);
|
||||
}
|
||||
else // receiving or paused
|
||||
} else // receiving or paused
|
||||
{
|
||||
if (btn->objectName() == "cancel")
|
||||
Core::getInstance()->cancelFileRecv(fileInfo.friendId, fileInfo.fileNum);
|
||||
@ -482,37 +478,31 @@ void FileTransferWidget::handleButton(QPushButton *btn)
|
||||
Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum);
|
||||
else if (btn->objectName() == "resume")
|
||||
Core::getInstance()->pauseResumeFileRecv(fileInfo.friendId, fileInfo.fileNum);
|
||||
else if (btn->objectName() == "accept")
|
||||
{
|
||||
QString path = QFileDialog::getSaveFileName(parentWidget(),
|
||||
tr("Save a file", "Title of the file saving dialog"),
|
||||
Settings::getInstance().getGlobalAutoAcceptDir() + "/" + fileInfo.fileName,
|
||||
0,
|
||||
0,
|
||||
QFileDialog::DontUseNativeDialog);
|
||||
else if (btn->objectName() == "accept") {
|
||||
QString path =
|
||||
QFileDialog::getSaveFileName(parentWidget(),
|
||||
tr("Save a file", "Title of the file saving dialog"),
|
||||
Settings::getInstance().getGlobalAutoAcceptDir() + "/"
|
||||
+ fileInfo.fileName,
|
||||
0, 0, QFileDialog::DontUseNativeDialog);
|
||||
acceptTransfer(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (btn->objectName() == "ok" || btn->objectName() == "previewButton")
|
||||
{
|
||||
if (btn->objectName() == "ok" || btn->objectName() == "previewButton") {
|
||||
Widget::confirmExecutableOpen(QFileInfo(fileInfo.filePath));
|
||||
}
|
||||
else if (btn->objectName() == "dir")
|
||||
{
|
||||
} else if (btn->objectName() == "dir") {
|
||||
QString dirPath = QFileInfo(fileInfo.filePath).dir().path();
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(dirPath));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FileTransferWidget::showPreview(const QString &filename)
|
||||
void FileTransferWidget::showPreview(const QString& filename)
|
||||
{
|
||||
static const QStringList previewExtensions = { "png", "jpeg", "jpg", "gif", "svg",
|
||||
"PNG", "JPEG", "JPG", "GIF", "SVG" };
|
||||
static const QStringList previewExtensions = {"png", "jpeg", "jpg", "gif", "svg",
|
||||
"PNG", "JPEG", "JPG", "GIF", "SVG"};
|
||||
|
||||
if (previewExtensions.contains(QFileInfo(filename).suffix()))
|
||||
{
|
||||
if (previewExtensions.contains(QFileInfo(filename).suffix())) {
|
||||
// Subtract to make border visible
|
||||
const int size = qMax(ui->previewButton->width(), ui->previewButton->height()) - 4;
|
||||
|
||||
@ -524,15 +514,15 @@ void FileTransferWidget::showPreview(const QString &filename)
|
||||
ui->previewButton->show();
|
||||
// Show mouseover preview, but make sure it's not larger than 50% of the screen width/height
|
||||
const QRect desktopSize = QApplication::desktop()->screenGeometry();
|
||||
const QImage previewImage = image.scaled(0.5 * desktopSize.width(),
|
||||
0.5 * desktopSize.height(),
|
||||
Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
const QImage previewImage = image.scaled(0.5 * desktopSize.width(), 0.5 * desktopSize.height(),
|
||||
Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
QByteArray imageData;
|
||||
QBuffer buffer(&imageData);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
previewImage.save(&buffer, "PNG");
|
||||
buffer.close();
|
||||
ui->previewButton->setToolTip("<img src=data:image/png;base64," + imageData.toBase64() + "/>");
|
||||
ui->previewButton->setToolTip("<img src=data:image/png;base64," + imageData.toBase64()
|
||||
+ "/>");
|
||||
}
|
||||
}
|
||||
|
||||
@ -551,19 +541,15 @@ void FileTransferWidget::onPreviewButtonClicked()
|
||||
handleButton(ui->previewButton);
|
||||
}
|
||||
|
||||
QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap &source, const int targetSize)
|
||||
QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap& source, const int targetSize)
|
||||
{
|
||||
QPixmap result;
|
||||
|
||||
// Make sure smaller-than-icon images (at least one dimension is smaller) will not be upscaled
|
||||
if (source.width() < targetSize || source.height() < targetSize)
|
||||
{
|
||||
if (source.width() < targetSize || source.height() < targetSize) {
|
||||
result = source;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = source.scaled(targetSize, targetSize,
|
||||
Qt::KeepAspectRatioByExpanding,
|
||||
} else {
|
||||
result = source.scaled(targetSize, targetSize, Qt::KeepAspectRatioByExpanding,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,8 @@
|
||||
#ifndef FILETRANSFERWIDGET_H
|
||||
#define FILETRANSFERWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTime>
|
||||
#include <QWidget>
|
||||
|
||||
#include "src/chatlog/chatlinecontent.h"
|
||||
#include "src/core/corestructs.h"
|
||||
@ -74,10 +74,10 @@ private slots:
|
||||
void onPreviewButtonClicked();
|
||||
|
||||
private:
|
||||
static QPixmap scaleCropIntoSquare(const QPixmap &source, int targetSize);
|
||||
static QPixmap scaleCropIntoSquare(const QPixmap& source, int targetSize);
|
||||
|
||||
private:
|
||||
Ui::FileTransferWidget *ui;
|
||||
Ui::FileTransferWidget* ui;
|
||||
ToxFile fileInfo;
|
||||
QTime lastTick;
|
||||
quint64 lastBytesSent = 0;
|
||||
|
@ -27,17 +27,17 @@
|
||||
class Image : public ChatLineContent
|
||||
{
|
||||
public:
|
||||
Image(QSize size, const QString &filename);
|
||||
Image(QSize size, const QString& filename);
|
||||
|
||||
virtual QRectF boundingRect() const override;
|
||||
virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
|
||||
virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
|
||||
QWidget* widget) override;
|
||||
virtual void setWidth(qreal width) override;
|
||||
virtual qreal getAscent() const override;
|
||||
|
||||
private:
|
||||
QSize size;
|
||||
QPixmap pmap;
|
||||
|
||||
};
|
||||
|
||||
#endif // IMAGE_H
|
||||
|
@ -20,9 +20,9 @@
|
||||
#include "notificationicon.h"
|
||||
#include "../pixmapcache.h"
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
#include <QGraphicsScene>
|
||||
|
||||
NotificationIcon::NotificationIcon(QSize Size)
|
||||
: size(Size)
|
||||
@ -30,7 +30,7 @@ NotificationIcon::NotificationIcon(QSize Size)
|
||||
pmap = PixmapCache::getInstance().get(":/ui/chatArea/typing.svg", size);
|
||||
|
||||
updateTimer = new QTimer(this);
|
||||
updateTimer->setInterval(1000/30);
|
||||
updateTimer->setInterval(1000 / 30);
|
||||
updateTimer->setSingleShot(false);
|
||||
|
||||
updateTimer->start();
|
||||
@ -74,7 +74,7 @@ void NotificationIcon::updateGradient()
|
||||
if (alpha + dotWidth >= 1.0)
|
||||
alpha = 0.0;
|
||||
|
||||
grad = QLinearGradient(QPointF(-0.5*size.width(),0), QPointF(3.0/2.0*size.width(),0));
|
||||
grad = QLinearGradient(QPointF(-0.5 * size.width(), 0), QPointF(3.0 / 2.0 * size.width(), 0));
|
||||
grad.setColorAt(0, Qt::lightGray);
|
||||
grad.setColorAt(qMax(0.0, alpha - dotWidth), Qt::lightGray);
|
||||
grad.setColorAt(alpha, Qt::black);
|
||||
|
@ -34,7 +34,8 @@ public:
|
||||
explicit NotificationIcon(QSize size);
|
||||
|
||||
virtual QRectF boundingRect() const override;
|
||||
virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
|
||||
virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
|
||||
QWidget* widget) override;
|
||||
virtual void setWidth(qreal width) override;
|
||||
virtual qreal getAscent() const override;
|
||||
|
||||
@ -49,7 +50,6 @@ private:
|
||||
|
||||
qreal dotWidth = 0.2;
|
||||
qreal alpha = 0.0;
|
||||
|
||||
};
|
||||
|
||||
#endif // NOTIFICATIONICON_H
|
||||
|
@ -20,19 +20,19 @@
|
||||
#include "spinner.h"
|
||||
#include "../pixmapcache.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsScene>
|
||||
#include <QPainter>
|
||||
#include <QTime>
|
||||
#include <QVariantAnimation>
|
||||
#include <QDebug>
|
||||
|
||||
Spinner::Spinner(const QString &img, QSize Size, qreal speed)
|
||||
Spinner::Spinner(const QString& img, QSize Size, qreal speed)
|
||||
: size(Size)
|
||||
, rotSpeed(speed)
|
||||
{
|
||||
pmap = PixmapCache::getInstance().get(img, size);
|
||||
|
||||
timer.setInterval(1000/30); // 30Hz
|
||||
timer.setInterval(1000 / 30); // 30Hz
|
||||
timer.setSingleShot(false);
|
||||
|
||||
blendAnimation = new QVariantAnimation(this);
|
||||
@ -41,7 +41,8 @@ Spinner::Spinner(const QString &img, QSize Size, qreal speed)
|
||||
blendAnimation->setDuration(350);
|
||||
blendAnimation->setEasingCurve(QEasingCurve::InCubic);
|
||||
blendAnimation->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
connect(blendAnimation, &QVariantAnimation::valueChanged, this, [this](const QVariant& val) { alpha = val.toDouble(); });
|
||||
connect(blendAnimation, &QVariantAnimation::valueChanged, this,
|
||||
[this](const QVariant& val) { alpha = val.toDouble(); });
|
||||
|
||||
QObject::connect(&timer, &QTimer::timeout, this, &Spinner::timeout);
|
||||
}
|
||||
@ -55,8 +56,9 @@ void Spinner::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, Q
|
||||
{
|
||||
painter->setClipRect(boundingRect());
|
||||
|
||||
QTransform trans = QTransform().rotate(QTime::currentTime().msecsSinceStartOfDay() / 1000.0 * rotSpeed)
|
||||
.translate(-size.width()/2.0, -size.height()/2.0);
|
||||
QTransform trans = QTransform()
|
||||
.rotate(QTime::currentTime().msecsSinceStartOfDay() / 1000.0 * rotSpeed)
|
||||
.translate(-size.width() / 2.0, -size.height() / 2.0);
|
||||
painter->setOpacity(alpha);
|
||||
painter->setTransform(trans, true);
|
||||
painter->setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
@ -22,9 +22,9 @@
|
||||
|
||||
#include "../chatlinecontent.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
#include <QTimer>
|
||||
|
||||
class QVariantAnimation;
|
||||
|
||||
@ -35,7 +35,8 @@ public:
|
||||
Spinner(const QString& img, QSize size, qreal speed);
|
||||
|
||||
virtual QRectF boundingRect() const override;
|
||||
virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
|
||||
virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
|
||||
QWidget* widget) override;
|
||||
virtual void setWidth(qreal width) override;
|
||||
virtual void visibilityChanged(bool visible) override;
|
||||
virtual qreal getAscent() const override;
|
||||
@ -50,7 +51,6 @@ private:
|
||||
QTimer timer;
|
||||
qreal alpha = 0.0;
|
||||
QVariantAnimation* blendAnimation;
|
||||
|
||||
};
|
||||
|
||||
#endif // SPINNER_H
|
||||
|
@ -20,20 +20,21 @@
|
||||
#include "text.h"
|
||||
#include "../documentcache.h"
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QDebug>
|
||||
#include <QTextBlock>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QApplication>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QFontMetrics>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QTextBlock>
|
||||
#include <QTextFragment>
|
||||
|
||||
#include "src/widget/style.h"
|
||||
|
||||
Text::Text(const QString& txt, const QFont& font, bool enableElide, const QString &rwText, const QColor c)
|
||||
Text::Text(const QString& txt, const QFont& font, bool enableElide, const QString& rwText,
|
||||
const QColor c)
|
||||
: rawText(rwText)
|
||||
, elide(enableElide)
|
||||
, defFont(font)
|
||||
@ -71,8 +72,7 @@ void Text::selectionMouseMove(QPointF scenePos)
|
||||
return;
|
||||
|
||||
int cur = cursorFromPos(scenePos);
|
||||
if (cur >= 0)
|
||||
{
|
||||
if (cur >= 0) {
|
||||
selectionEnd = cur;
|
||||
selectedText = extractSanitizedText(getSelectionStart(), getSelectionEnd());
|
||||
}
|
||||
@ -83,8 +83,7 @@ void Text::selectionMouseMove(QPointF scenePos)
|
||||
void Text::selectionStarted(QPointF scenePos)
|
||||
{
|
||||
int cur = cursorFromPos(scenePos);
|
||||
if (cur >= 0)
|
||||
{
|
||||
if (cur >= 0) {
|
||||
selectionEnd = cur;
|
||||
selectionAnchor = cur;
|
||||
}
|
||||
@ -108,8 +107,7 @@ void Text::selectionDoubleClick(QPointF scenePos)
|
||||
|
||||
int cur = cursorFromPos(scenePos);
|
||||
|
||||
if (cur >= 0)
|
||||
{
|
||||
if (cur >= 0) {
|
||||
QTextCursor cursor(doc);
|
||||
cursor.setPosition(cur);
|
||||
cursor.select(QTextCursor::WordUnderCursor);
|
||||
@ -167,8 +165,7 @@ void Text::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWid
|
||||
QAbstractTextDocumentLayout::PaintContext ctx;
|
||||
QAbstractTextDocumentLayout::Selection sel;
|
||||
|
||||
if (hasSelection())
|
||||
{
|
||||
if (hasSelection()) {
|
||||
sel.cursor = QTextCursor(doc);
|
||||
sel.cursor.setPosition(getSelectionStart());
|
||||
sel.cursor.setPosition(getSelectionEnd(), QTextCursor::KeepAnchor);
|
||||
@ -197,13 +194,13 @@ qreal Text::getAscent() const
|
||||
return ascent;
|
||||
}
|
||||
|
||||
void Text::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
void Text::mousePressEvent(QGraphicsSceneMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
event->accept(); // grabber
|
||||
}
|
||||
|
||||
void Text::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
void Text::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
|
||||
{
|
||||
if (!doc)
|
||||
return;
|
||||
@ -215,7 +212,7 @@ void Text::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
QDesktopServices::openUrl(anchor);
|
||||
}
|
||||
|
||||
void Text::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
|
||||
void Text::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
|
||||
{
|
||||
if (!doc)
|
||||
return;
|
||||
@ -250,25 +247,20 @@ QString Text::getLinkAt(QPointF scenePos) const
|
||||
|
||||
void Text::regenerate()
|
||||
{
|
||||
if (!doc)
|
||||
{
|
||||
if (!doc) {
|
||||
doc = DocumentCache::getInstance().pop();
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
if (dirty) {
|
||||
doc->setDefaultFont(defFont);
|
||||
|
||||
if (elide)
|
||||
{
|
||||
if (elide) {
|
||||
QFontMetrics metrics = QFontMetrics(defFont);
|
||||
QString elidedText = metrics.elidedText(text, Qt::ElideRight, qRound(width));
|
||||
|
||||
doc->setPlainText(elidedText);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
doc->setDefaultStyleSheet(defStyleSheet);
|
||||
doc->setHtml(text);
|
||||
}
|
||||
@ -318,7 +310,8 @@ QSizeF Text::idealSize()
|
||||
int Text::cursorFromPos(QPointF scenePos, bool fuzzy) const
|
||||
{
|
||||
if (doc)
|
||||
return doc->documentLayout()->hitTest(mapFromScene(scenePos), fuzzy ? Qt::FuzzyHit : Qt::ExactHit);
|
||||
return doc->documentLayout()->hitTest(mapFromScene(scenePos),
|
||||
fuzzy ? Qt::FuzzyHit : Qt::ExactHit);
|
||||
|
||||
return -1;
|
||||
}
|
||||
@ -346,26 +339,22 @@ QString Text::extractSanitizedText(int from, int to) const
|
||||
QString txt;
|
||||
QTextBlock block = doc->firstBlock();
|
||||
|
||||
for (QTextBlock::Iterator itr = block.begin(); itr!=block.end(); ++itr)
|
||||
{
|
||||
int pos = itr.fragment().position(); //fragment position -> position of the first character in the fragment
|
||||
for (QTextBlock::Iterator itr = block.begin(); itr != block.end(); ++itr) {
|
||||
int pos =
|
||||
itr.fragment()
|
||||
.position(); // fragment position -> position of the first character in the fragment
|
||||
|
||||
if (itr.fragment().charFormat().isImageFormat())
|
||||
{
|
||||
if (itr.fragment().charFormat().isImageFormat()) {
|
||||
QTextImageFormat imgFmt = itr.fragment().charFormat().toImageFormat();
|
||||
QString key = imgFmt.name(); //img key (eg. key::D for :D)
|
||||
QString key = imgFmt.name(); // img key (eg. key::D for :D)
|
||||
QString rune = key.mid(4);
|
||||
|
||||
if (pos >= from && pos < to)
|
||||
{
|
||||
if (pos >= from && pos < to) {
|
||||
txt += rune;
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (QChar c : itr.fragment().text())
|
||||
{
|
||||
} else {
|
||||
for (QChar c : itr.fragment().text()) {
|
||||
if (pos >= from && pos < to)
|
||||
txt += c;
|
||||
|
||||
@ -379,10 +368,8 @@ QString Text::extractSanitizedText(int from, int to) const
|
||||
|
||||
QString Text::extractImgTooltip(int pos) const
|
||||
{
|
||||
for (QTextBlock::Iterator itr = doc->firstBlock().begin(); itr!=doc->firstBlock().end(); ++itr)
|
||||
{
|
||||
if (itr.fragment().contains(pos) && itr.fragment().charFormat().isImageFormat())
|
||||
{
|
||||
for (QTextBlock::Iterator itr = doc->firstBlock().begin(); itr != doc->firstBlock().end(); ++itr) {
|
||||
if (itr.fragment().contains(pos) && itr.fragment().charFormat().isImageFormat()) {
|
||||
QTextImageFormat imgFmt = itr.fragment().charFormat().toImageFormat();
|
||||
return imgFmt.toolTip();
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ class Text : public ChatLineContent
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Text(const QString& txt = "", const QFont& font = QFont(), bool enableElide = false, const QString& rawText = QString(), const QColor c = Qt::black);
|
||||
Text(const QString& txt = "", const QFont& font = QFont(), bool enableElide = false,
|
||||
const QString& rawText = QString(), const QColor c = Qt::black);
|
||||
virtual ~Text();
|
||||
|
||||
void setText(const QString& txt);
|
||||
@ -53,8 +54,8 @@ public:
|
||||
virtual void visibilityChanged(bool keepInMemory) final;
|
||||
|
||||
virtual qreal getAscent() const final;
|
||||
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) final override;
|
||||
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) final override;
|
||||
virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) final override;
|
||||
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) final override;
|
||||
void hoverMoveEvent(QGraphicsSceneHoverEvent* event) final override;
|
||||
|
||||
virtual QString getText() const final;
|
||||
@ -90,7 +91,6 @@ private:
|
||||
QFont defFont;
|
||||
QString defStyleSheet;
|
||||
QColor color;
|
||||
|
||||
};
|
||||
|
||||
#endif // TEXT_H
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
#include "timestamp.h"
|
||||
|
||||
Timestamp::Timestamp(const QDateTime &time, const QString &format, const QFont &font)
|
||||
Timestamp::Timestamp(const QDateTime& time, const QString& format, const QFont& font)
|
||||
: Text(time.toString(format), font, false, time.toString(format))
|
||||
{
|
||||
this->time = time;
|
||||
|
@ -20,8 +20,8 @@
|
||||
#ifndef TIMESTAMP_H
|
||||
#define TIMESTAMP_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include "text.h"
|
||||
#include <QDateTime>
|
||||
|
||||
class Timestamp : public Text
|
||||
{
|
||||
|
@ -22,22 +22,22 @@
|
||||
#include "src/persistence/smileypack.h"
|
||||
#include "src/widget/style.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
#include <QUrl>
|
||||
|
||||
CustomTextDocument::CustomTextDocument(QObject *parent)
|
||||
CustomTextDocument::CustomTextDocument(QObject* parent)
|
||||
: QTextDocument(parent)
|
||||
{
|
||||
setUndoRedoEnabled(false);
|
||||
setUseDesignMetrics(false);
|
||||
}
|
||||
|
||||
QVariant CustomTextDocument::loadResource(int type, const QUrl &name)
|
||||
QVariant CustomTextDocument::loadResource(int type, const QUrl& name)
|
||||
{
|
||||
if (type == QTextDocument::ImageResource && name.scheme() == "key")
|
||||
{
|
||||
QSize size = QSize(Settings::getInstance().getEmojiFontPointSize(),Settings::getInstance().getEmojiFontPointSize());
|
||||
if (type == QTextDocument::ImageResource && name.scheme() == "key") {
|
||||
QSize size = QSize(Settings::getInstance().getEmojiFontPointSize(),
|
||||
Settings::getInstance().getEmojiFontPointSize());
|
||||
QString fileName = QUrl::fromPercentEncoding(name.toEncoded()).mid(4).toHtmlEscaped();
|
||||
|
||||
return SmileyPack::getInstance().getAsIcon(fileName).pixmap(size);
|
||||
|
@ -26,10 +26,10 @@ class CustomTextDocument : public QTextDocument
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CustomTextDocument(QObject *parent = 0);
|
||||
explicit CustomTextDocument(QObject* parent = 0);
|
||||
|
||||
protected:
|
||||
virtual QVariant loadResource(int type, const QUrl &name);
|
||||
virtual QVariant loadResource(int type, const QUrl& name);
|
||||
};
|
||||
|
||||
#endif // CUSTOMTEXTDOCUMENT_H
|
||||
|
@ -34,10 +34,9 @@ QTextDocument* DocumentCache::pop()
|
||||
return documents.pop();
|
||||
}
|
||||
|
||||
void DocumentCache::push(QTextDocument *doc)
|
||||
void DocumentCache::push(QTextDocument* doc)
|
||||
{
|
||||
if (doc)
|
||||
{
|
||||
if (doc) {
|
||||
doc->clear();
|
||||
documents.push(doc);
|
||||
}
|
||||
@ -46,7 +45,7 @@ void DocumentCache::push(QTextDocument *doc)
|
||||
/**
|
||||
* @brief Returns the singleton instance.
|
||||
*/
|
||||
DocumentCache &DocumentCache::getInstance()
|
||||
DocumentCache& DocumentCache::getInstance()
|
||||
{
|
||||
static DocumentCache instance;
|
||||
return instance;
|
||||
|
@ -19,12 +19,11 @@
|
||||
|
||||
#include "pixmapcache.h"
|
||||
|
||||
QPixmap PixmapCache::get(const QString &filename, QSize size)
|
||||
QPixmap PixmapCache::get(const QString& filename, QSize size)
|
||||
{
|
||||
auto itr = cache.find(filename);
|
||||
|
||||
if (itr == cache.end())
|
||||
{
|
||||
if (itr == cache.end()) {
|
||||
QIcon icon;
|
||||
icon.addFile(filename);
|
||||
|
||||
@ -38,9 +37,8 @@ QPixmap PixmapCache::get(const QString &filename, QSize size)
|
||||
/**
|
||||
* @brief Returns the singleton instance.
|
||||
*/
|
||||
PixmapCache &PixmapCache::getInstance()
|
||||
PixmapCache& PixmapCache::getInstance()
|
||||
{
|
||||
static PixmapCache instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,9 @@
|
||||
#ifndef ICONCACHE_H
|
||||
#define ICONCACHE_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
#include <QHash>
|
||||
|
||||
class PixmapCache
|
||||
{
|
||||
@ -31,7 +31,9 @@ public:
|
||||
static PixmapCache& getInstance();
|
||||
|
||||
protected:
|
||||
PixmapCache() {}
|
||||
PixmapCache()
|
||||
{
|
||||
}
|
||||
PixmapCache(PixmapCache&) = delete;
|
||||
PixmapCache& operator=(const PixmapCache&) = delete;
|
||||
|
||||
|
@ -24,7 +24,8 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QVector>
|
||||
|
||||
enum TextStyle {
|
||||
enum TextStyle
|
||||
{
|
||||
BOLD = 0,
|
||||
ITALIC,
|
||||
UNDERLINE,
|
||||
@ -49,31 +50,27 @@ static const QString MULTILINE_CODE = QStringLiteral("(?<=^|[^`])"
|
||||
"(?=$|[^`])");
|
||||
|
||||
// Items in vector associated with TextStyle values respectively. Do NOT change this order
|
||||
static const QVector<QString> fontStylePatterns
|
||||
{
|
||||
QStringLiteral("<b>%1</b>"),
|
||||
QStringLiteral("<i>%1</i>"),
|
||||
QStringLiteral("<u>%1</u>"),
|
||||
QStringLiteral("<s>%1</s>"),
|
||||
QStringLiteral("<font color=#595959><code>%1</code></font>")
|
||||
};
|
||||
static const QVector<QString> fontStylePatterns{QStringLiteral("<b>%1</b>"),
|
||||
QStringLiteral("<i>%1</i>"),
|
||||
QStringLiteral("<u>%1</u>"),
|
||||
QStringLiteral("<s>%1</s>"),
|
||||
QStringLiteral(
|
||||
"<font color=#595959><code>%1</code></font>")};
|
||||
|
||||
// Unfortunately, can't use simple QMap because ordered applying of styles is required
|
||||
static const QVector<QPair<QRegularExpression, QString>> textPatternStyle
|
||||
{
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("*", "1")), fontStylePatterns[BOLD] },
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("/", "1")), fontStylePatterns[ITALIC] },
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("_", "1")), fontStylePatterns[UNDERLINE] },
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("~", "1")), fontStylePatterns[STRIKE] },
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("`", "1")), fontStylePatterns[CODE] },
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("*", "2")), fontStylePatterns[BOLD] },
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("/", "2")), fontStylePatterns[ITALIC] },
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("_", "2")), fontStylePatterns[UNDERLINE] },
|
||||
{ QRegularExpression(COMMON_PATTERN.arg("~", "2")), fontStylePatterns[STRIKE] },
|
||||
{ QRegularExpression(MULTILINE_CODE), fontStylePatterns[CODE] }
|
||||
};
|
||||
static const QVector<QPair<QRegularExpression, QString>> textPatternStyle{
|
||||
{QRegularExpression(COMMON_PATTERN.arg("*", "1")), fontStylePatterns[BOLD]},
|
||||
{QRegularExpression(COMMON_PATTERN.arg("/", "1")), fontStylePatterns[ITALIC]},
|
||||
{QRegularExpression(COMMON_PATTERN.arg("_", "1")), fontStylePatterns[UNDERLINE]},
|
||||
{QRegularExpression(COMMON_PATTERN.arg("~", "1")), fontStylePatterns[STRIKE]},
|
||||
{QRegularExpression(COMMON_PATTERN.arg("`", "1")), fontStylePatterns[CODE]},
|
||||
{QRegularExpression(COMMON_PATTERN.arg("*", "2")), fontStylePatterns[BOLD]},
|
||||
{QRegularExpression(COMMON_PATTERN.arg("/", "2")), fontStylePatterns[ITALIC]},
|
||||
{QRegularExpression(COMMON_PATTERN.arg("_", "2")), fontStylePatterns[UNDERLINE]},
|
||||
{QRegularExpression(COMMON_PATTERN.arg("~", "2")), fontStylePatterns[STRIKE]},
|
||||
{QRegularExpression(MULTILINE_CODE), fontStylePatterns[CODE]}};
|
||||
|
||||
TextFormatter::TextFormatter(const QString &str)
|
||||
TextFormatter::TextFormatter(const QString& str)
|
||||
: sourceString(str)
|
||||
{
|
||||
}
|
||||
@ -88,8 +85,7 @@ static int patternSignsCount(const QString& str)
|
||||
QChar escapeSign = str.at(0);
|
||||
int result = 0;
|
||||
int length = str.length();
|
||||
while (result < length && str[result] == escapeSign)
|
||||
{
|
||||
while (result < length && str[result] == escapeSign) {
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
@ -108,34 +104,29 @@ static bool isTagIntersection(const QString& str)
|
||||
int closingTagCount = 0;
|
||||
|
||||
QRegularExpressionMatchIterator iter = TAG_PATTERN.globalMatch(str);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
iter.next().captured()[0] == '/'
|
||||
? ++closingTagCount
|
||||
: ++openingTagCount;
|
||||
while (iter.hasNext()) {
|
||||
iter.next().captured()[0] == '/' ? ++closingTagCount : ++openingTagCount;
|
||||
}
|
||||
return openingTagCount != closingTagCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies styles to the font of text that was passed to the constructor
|
||||
* @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting string
|
||||
* @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting
|
||||
* string
|
||||
* @return Source text with styled font
|
||||
*/
|
||||
QString TextFormatter::applyHtmlFontStyling(bool showFormattingSymbols)
|
||||
{
|
||||
QString out = sourceString;
|
||||
|
||||
for (QPair<QRegularExpression, QString> pair : textPatternStyle)
|
||||
{
|
||||
for (QPair<QRegularExpression, QString> pair : textPatternStyle) {
|
||||
QRegularExpressionMatchIterator matchesIterator = pair.first.globalMatch(out);
|
||||
int insertedTagSymbolsCount = 0;
|
||||
|
||||
while (matchesIterator.hasNext())
|
||||
{
|
||||
while (matchesIterator.hasNext()) {
|
||||
QRegularExpressionMatch match = matchesIterator.next();
|
||||
if (isTagIntersection(match.captured()))
|
||||
{
|
||||
if (isTagIntersection(match.captured())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -159,7 +150,8 @@ QString TextFormatter::applyHtmlFontStyling(bool showFormattingSymbols)
|
||||
|
||||
/**
|
||||
* @brief Applies all styling for the text
|
||||
* @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting string
|
||||
* @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting
|
||||
* string
|
||||
* @return Styled string
|
||||
*/
|
||||
QString TextFormatter::applyStyling(bool showFormattingSymbols)
|
||||
|
@ -25,7 +25,6 @@
|
||||
class TextFormatter
|
||||
{
|
||||
private:
|
||||
|
||||
QString sourceString;
|
||||
|
||||
QString applyHtmlFontStyling(bool showFormattingSymbols);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,19 +21,20 @@
|
||||
#ifndef CORE_HPP
|
||||
#define CORE_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <cstdint>
|
||||
|
||||
#include <tox/tox.h>
|
||||
#include <tox/toxencryptsave.h>
|
||||
|
||||
#include "corestructs.h"
|
||||
#include "coredefines.h"
|
||||
#include "corestructs.h"
|
||||
#include "toxid.h"
|
||||
|
||||
class Profile;
|
||||
template <typename T> class QList;
|
||||
template <typename T>
|
||||
class QList;
|
||||
class QTimer;
|
||||
class QString;
|
||||
class CString;
|
||||
@ -54,7 +55,7 @@ public:
|
||||
static const QString TOX_EXT;
|
||||
static const QString CONFIG_FILE_NAME;
|
||||
static QString sanitize(QString name);
|
||||
static QList<CString> splitMessage(const QString &message, int maxLen);
|
||||
static QList<CString> splitMessage(const QString& message, int maxLen);
|
||||
|
||||
static QByteArray getSaltFromFile(QString filename);
|
||||
|
||||
@ -69,8 +70,8 @@ public:
|
||||
QString getFriendUsername(uint32_t friendNumber) const;
|
||||
|
||||
bool isFriendOnline(uint32_t friendId) const;
|
||||
bool hasFriendWithPublicKey(const ToxPk &publicKey) const;
|
||||
uint32_t joinGroupchat(int32_t friendId, uint8_t type, const uint8_t* pubkey,uint16_t length) const;
|
||||
bool hasFriendWithPublicKey(const ToxPk& publicKey) const;
|
||||
uint32_t joinGroupchat(int32_t friendId, uint8_t type, const uint8_t* pubkey, uint16_t length) const;
|
||||
void quitGroupChat(int groupId) const;
|
||||
|
||||
QString getUsername() const;
|
||||
@ -91,8 +92,8 @@ public slots:
|
||||
|
||||
QByteArray getToxSaveData();
|
||||
|
||||
void acceptFriendRequest(const ToxPk &friendPk);
|
||||
void requestFriendship(const ToxId &friendAddress, const QString& message);
|
||||
void acceptFriendRequest(const ToxPk& friendPk);
|
||||
void requestFriendship(const ToxId& friendAddress, const QString& message);
|
||||
void groupInviteFriend(uint32_t friendId, int groupId);
|
||||
int createGroup(uint8_t type = TOX_CONFERENCE_TYPE_AV);
|
||||
|
||||
@ -186,35 +187,28 @@ signals:
|
||||
void fileSendFailed(uint32_t friendId, const QString& fname);
|
||||
|
||||
private:
|
||||
static void onFriendRequest(Tox* tox, const uint8_t* cUserId,
|
||||
const uint8_t* cMessage, size_t cMessageSize,
|
||||
void* core);
|
||||
static void onFriendMessage(Tox* tox, uint32_t friendId,
|
||||
TOX_MESSAGE_TYPE type, const uint8_t* cMessage,
|
||||
static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage,
|
||||
size_t cMessageSize, void* core);
|
||||
static void onFriendNameChange(Tox* tox, uint32_t friendId,
|
||||
const uint8_t* cName, size_t cNameSize,
|
||||
void* core);
|
||||
static void onFriendTypingChange(Tox* tox, uint32_t friendId, bool isTyping,
|
||||
void* core);
|
||||
static void onStatusMessageChanged(Tox* tox, uint32_t friendId,
|
||||
const uint8_t* cMessage,
|
||||
static void onFriendMessage(Tox* tox, uint32_t friendId, TOX_MESSAGE_TYPE type,
|
||||
const uint8_t* cMessage, size_t cMessageSize, void* core);
|
||||
static void onFriendNameChange(Tox* tox, uint32_t friendId, const uint8_t* cName,
|
||||
size_t cNameSize, void* core);
|
||||
static void onFriendTypingChange(Tox* tox, uint32_t friendId, bool isTyping, void* core);
|
||||
static void onStatusMessageChanged(Tox* tox, uint32_t friendId, const uint8_t* cMessage,
|
||||
size_t cMessageSize, void* core);
|
||||
static void onUserStatusChanged(Tox* tox, uint32_t friendId,
|
||||
TOX_USER_STATUS userstatus, void* core);
|
||||
static void onConnectionStatusChanged(Tox* tox, uint32_t friendId,
|
||||
TOX_CONNECTION status, void* core);
|
||||
static void onUserStatusChanged(Tox* tox, uint32_t friendId, TOX_USER_STATUS userstatus,
|
||||
void* core);
|
||||
static void onConnectionStatusChanged(Tox* tox, uint32_t friendId, TOX_CONNECTION status,
|
||||
void* core);
|
||||
static void onGroupInvite(Tox* tox, uint32_t friendId, TOX_CONFERENCE_TYPE type,
|
||||
const uint8_t* data, size_t length, void* vCore);
|
||||
static void onGroupMessage(Tox* tox, uint32_t groupId, uint32_t peerId,
|
||||
TOX_MESSAGE_TYPE type, const uint8_t* cMessage,
|
||||
size_t length, void* vCore);
|
||||
static void onGroupMessage(Tox* tox, uint32_t groupId, uint32_t peerId, TOX_MESSAGE_TYPE type,
|
||||
const uint8_t* cMessage, size_t length, void* vCore);
|
||||
static void onGroupNamelistChange(Tox* tox, uint32_t groupId, uint32_t peerId,
|
||||
TOX_CONFERENCE_STATE_CHANGE change, void* core);
|
||||
static void onGroupTitleChange(Tox* tox, uint32_t groupId, uint32_t peerId,
|
||||
const uint8_t* cTitle, size_t length, void* vCore);
|
||||
static void onReadReceiptCallback(Tox* tox, uint32_t friendId,
|
||||
uint32_t receipt, void* core);
|
||||
static void onReadReceiptCallback(Tox* tox, uint32_t friendId, uint32_t receipt, void* core);
|
||||
|
||||
void sendGroupMessageWithType(int groupId, const QString& message, TOX_MESSAGE_TYPE type);
|
||||
bool parsePeerQueryError(TOX_ERR_CONFERENCE_PEER_QUERY error) const;
|
||||
@ -235,17 +229,16 @@ private slots:
|
||||
private:
|
||||
Tox* tox;
|
||||
CoreAV* av;
|
||||
QTimer *toxTimer;
|
||||
QTimer* toxTimer;
|
||||
Profile& profile;
|
||||
QMutex messageSendMutex;
|
||||
bool ready;
|
||||
|
||||
static QThread *coreThread;
|
||||
static QThread* coreThread;
|
||||
|
||||
friend class Audio; ///< Audio can access our calls directly to reduce latency
|
||||
friend class Audio; ///< Audio can access our calls directly to reduce latency
|
||||
friend class CoreFile; ///< CoreFile can access tox* and emit our signals
|
||||
friend class CoreAV; ///< CoreAV accesses our toxav* for now
|
||||
friend class CoreAV; ///< CoreAV accesses our toxav* for now
|
||||
};
|
||||
|
||||
#endif // CORE_HPP
|
||||
|
||||
|
@ -24,14 +24,14 @@
|
||||
#include "src/friend.h"
|
||||
#include "src/group.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/video/videoframe.h"
|
||||
#include "src/video/corevideosource.h"
|
||||
#include <cassert>
|
||||
#include "src/video/videoframe.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
#include <QCoreApplication>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <cassert>
|
||||
|
||||
/**
|
||||
* @fn void CoreAV::avInvite(uint32_t friendId, bool video)
|
||||
@ -57,16 +57,19 @@
|
||||
|
||||
/**
|
||||
* @var std::atomic_flag CoreAV::threadSwitchLock
|
||||
* @brief This flag is to be acquired before switching in a blocking way between the UI and CoreAV thread.
|
||||
* @brief This flag is to be acquired before switching in a blocking way between the UI and CoreAV
|
||||
* thread.
|
||||
*
|
||||
* The CoreAV thread must have priority for the flag, other threads should back off or release it quickly.
|
||||
* The CoreAV thread must have priority for the flag, other threads should back off or release it
|
||||
* quickly.
|
||||
* CoreAV needs to interface with three threads, the toxcore/Core thread that fires non-payload
|
||||
* toxav callbacks, the toxav/CoreAV thread that fires AV payload callbacks and manages
|
||||
* most of CoreAV's members, and the UI thread, which calls our [start/answer/cancel]Call functions
|
||||
* and which we call via signals.
|
||||
* When the UI calls us, we switch from the UI thread to the CoreAV thread to do the processing,
|
||||
* when toxcore fires a non-payload av callback, we do the processing in the CoreAV thread and then
|
||||
* switch to the UI thread to send it a signal. Both switches block both threads, so this would deadlock.
|
||||
* switch to the UI thread to send it a signal. Both switches block both threads, so this would
|
||||
* deadlock.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -79,9 +82,10 @@ IndexedList<ToxFriendCall> CoreAV::calls;
|
||||
*/
|
||||
IndexedList<ToxGroupCall> CoreAV::groupCalls;
|
||||
|
||||
CoreAV::CoreAV(Tox *tox)
|
||||
: coreavThread{new QThread}, iterateTimer{new QTimer{this}},
|
||||
threadSwitchLock{false}
|
||||
CoreAV::CoreAV(Tox* tox)
|
||||
: coreavThread{new QThread}
|
||||
, iterateTimer{new QTimer{this}}
|
||||
, threadSwitchLock{false}
|
||||
{
|
||||
coreavThread->setObjectName("qTox CoreAV");
|
||||
moveToThread(coreavThread.get());
|
||||
@ -107,14 +111,13 @@ CoreAV::~CoreAV()
|
||||
killTimerFromThread();
|
||||
toxav_kill(toxav);
|
||||
coreavThread->exit(0);
|
||||
while (coreavThread->isRunning())
|
||||
{
|
||||
while (coreavThread->isRunning()) {
|
||||
qApp->processEvents();
|
||||
coreavThread->wait(100);
|
||||
}
|
||||
}
|
||||
|
||||
const ToxAV *CoreAV::getToxAv() const
|
||||
const ToxAV* CoreAV::getToxAv() const
|
||||
{
|
||||
return toxav;
|
||||
}
|
||||
@ -148,7 +151,8 @@ void CoreAV::killTimerFromThread()
|
||||
{
|
||||
// Timers can only be touched from their own thread
|
||||
if (QThread::currentThread() != coreavThread.get())
|
||||
return (void)QMetaObject::invokeMethod(this, "killTimerFromThread", Qt::BlockingQueuedConnection);
|
||||
return (void)QMetaObject::invokeMethod(this, "killTimerFromThread",
|
||||
Qt::BlockingQueuedConnection);
|
||||
iterateTimer.release();
|
||||
}
|
||||
|
||||
@ -195,9 +199,7 @@ bool CoreAV::isCallStarted(const Group* g) const
|
||||
*/
|
||||
bool CoreAV::isCallActive(const Friend* f) const
|
||||
{
|
||||
return isCallStarted(f)
|
||||
? !(calls[f->getFriendId()].inactive)
|
||||
: false;
|
||||
return isCallStarted(f) ? !(calls[f->getFriendId()].inactive) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,24 +209,18 @@ bool CoreAV::isCallActive(const Friend* f) const
|
||||
*/
|
||||
bool CoreAV::isCallActive(const Group* g) const
|
||||
{
|
||||
return isCallStarted(g)
|
||||
? !(groupCalls[g->getGroupId()].inactive)
|
||||
: false;
|
||||
return isCallStarted(g) ? !(groupCalls[g->getGroupId()].inactive) : false;
|
||||
}
|
||||
|
||||
bool CoreAV::isCallVideoEnabled(const Friend* f) const
|
||||
{
|
||||
return isCallStarted(f)
|
||||
? calls[f->getFriendId()].videoEnabled
|
||||
: false;
|
||||
return isCallStarted(f) ? calls[f->getFriendId()].videoEnabled : false;
|
||||
}
|
||||
|
||||
bool CoreAV::answerCall(uint32_t friendNum)
|
||||
{
|
||||
if (QThread::currentThread() != coreavThread.get())
|
||||
{
|
||||
if (threadSwitchLock.test_and_set(std::memory_order_acquire))
|
||||
{
|
||||
if (QThread::currentThread() != coreavThread.get()) {
|
||||
if (threadSwitchLock.test_and_set(std::memory_order_acquire)) {
|
||||
qDebug() << "CoreAV::answerCall: Backed off of thread-switch lock";
|
||||
return false;
|
||||
}
|
||||
@ -240,14 +236,11 @@ bool CoreAV::answerCall(uint32_t friendNum)
|
||||
qDebug() << QString("answering call %1").arg(friendNum);
|
||||
assert(calls.contains(friendNum));
|
||||
TOXAV_ERR_ANSWER err;
|
||||
if (toxav_answer(toxav, friendNum, AUDIO_DEFAULT_BITRATE, VIDEO_DEFAULT_BITRATE, &err))
|
||||
{
|
||||
if (toxav_answer(toxav, friendNum, AUDIO_DEFAULT_BITRATE, VIDEO_DEFAULT_BITRATE, &err)) {
|
||||
calls[friendNum].inactive = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Failed to answer call with error"<<err;
|
||||
} else {
|
||||
qWarning() << "Failed to answer call with error" << err;
|
||||
toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr);
|
||||
calls.remove(friendNum);
|
||||
return false;
|
||||
@ -256,18 +249,15 @@ bool CoreAV::answerCall(uint32_t friendNum)
|
||||
|
||||
bool CoreAV::startCall(uint32_t friendNum, bool video)
|
||||
{
|
||||
if (QThread::currentThread() != coreavThread.get())
|
||||
{
|
||||
if (threadSwitchLock.test_and_set(std::memory_order_acquire))
|
||||
{
|
||||
if (QThread::currentThread() != coreavThread.get()) {
|
||||
if (threadSwitchLock.test_and_set(std::memory_order_acquire)) {
|
||||
qDebug() << "CoreAV::startCall: Backed off of thread-switch lock";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret;
|
||||
QMetaObject::invokeMethod(this, "startCall", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, ret),
|
||||
Q_ARG(uint32_t, friendNum),
|
||||
Q_RETURN_ARG(bool, ret), Q_ARG(uint32_t, friendNum),
|
||||
Q_ARG(bool, video));
|
||||
|
||||
threadSwitchLock.clear(std::memory_order_release);
|
||||
@ -275,8 +265,7 @@ bool CoreAV::startCall(uint32_t friendNum, bool video)
|
||||
}
|
||||
|
||||
qDebug() << QString("Starting call with %1").arg(friendNum);
|
||||
if (calls.contains(friendNum))
|
||||
{
|
||||
if (calls.contains(friendNum)) {
|
||||
qWarning() << QString("Can't start call with %1, we're already in this call!").arg(friendNum);
|
||||
return false;
|
||||
}
|
||||
@ -292,27 +281,22 @@ bool CoreAV::startCall(uint32_t friendNum, bool video)
|
||||
|
||||
bool CoreAV::cancelCall(uint32_t friendNum)
|
||||
{
|
||||
if (QThread::currentThread() != coreavThread.get())
|
||||
{
|
||||
if (threadSwitchLock.test_and_set(std::memory_order_acquire))
|
||||
{
|
||||
if (QThread::currentThread() != coreavThread.get()) {
|
||||
if (threadSwitchLock.test_and_set(std::memory_order_acquire)) {
|
||||
qDebug() << "CoreAV::cancelCall: Backed off of thread-switch lock";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret;
|
||||
QMetaObject::invokeMethod(this, "cancelCall",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, ret),
|
||||
Q_ARG(uint32_t, friendNum));
|
||||
QMetaObject::invokeMethod(this, "cancelCall", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, ret), Q_ARG(uint32_t, friendNum));
|
||||
|
||||
threadSwitchLock.clear(std::memory_order_release);
|
||||
return ret;
|
||||
}
|
||||
|
||||
qDebug() << QString("Cancelling call with %1").arg(friendNum);
|
||||
if (!toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr))
|
||||
{
|
||||
if (!toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr)) {
|
||||
qWarning() << QString("Failed to cancel call with %1").arg(friendNum);
|
||||
return false;
|
||||
}
|
||||
@ -326,19 +310,17 @@ void CoreAV::timeoutCall(uint32_t friendNum)
|
||||
{
|
||||
// Non-blocking switch to the CoreAV thread, we really don't want to be coming
|
||||
// blocking-queued from the UI thread while we emit blocking-queued to it
|
||||
if (QThread::currentThread() != coreavThread.get())
|
||||
{
|
||||
if (QThread::currentThread() != coreavThread.get()) {
|
||||
QMetaObject::invokeMethod(this, "timeoutCall", Qt::QueuedConnection,
|
||||
Q_ARG(uint32_t, friendNum));
|
||||
Q_ARG(uint32_t, friendNum));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cancelCall(friendNum))
|
||||
{
|
||||
if (!cancelCall(friendNum)) {
|
||||
qWarning() << QString("Failed to timeout call with %1").arg(friendNum);
|
||||
return;
|
||||
}
|
||||
qDebug() << "Call with friend"<<friendNum<<"timed out";
|
||||
qDebug() << "Call with friend" << friendNum << "timed out";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -350,16 +332,15 @@ void CoreAV::timeoutCall(uint32_t friendNum)
|
||||
* @param rate Audio sampling rate used in this frame.
|
||||
* @return False only on error, but not if there's nothing to send.
|
||||
*/
|
||||
bool CoreAV::sendCallAudio(uint32_t callId, const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate)
|
||||
bool CoreAV::sendCallAudio(uint32_t callId, const int16_t* pcm, size_t samples, uint8_t chans,
|
||||
uint32_t rate)
|
||||
{
|
||||
if (!calls.contains(callId))
|
||||
return false;
|
||||
|
||||
ToxFriendCall& call = calls[callId];
|
||||
|
||||
if (call.muteMic || call.inactive
|
||||
|| !(call.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A))
|
||||
{
|
||||
if (call.muteMic || call.inactive || !(call.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -367,16 +348,12 @@ bool CoreAV::sendCallAudio(uint32_t callId, const int16_t *pcm, size_t samples,
|
||||
TOXAV_ERR_SEND_FRAME err;
|
||||
int retries = 0;
|
||||
do {
|
||||
if (!toxav_audio_send_frame(toxav, callId, pcm, samples, chans, rate, &err))
|
||||
{
|
||||
if (err == TOXAV_ERR_SEND_FRAME_SYNC)
|
||||
{
|
||||
if (!toxav_audio_send_frame(toxav, callId, pcm, samples, chans, rate, &err)) {
|
||||
if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
|
||||
++retries;
|
||||
QThread::usleep(500);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "toxav_audio_send_frame error: "<<err;
|
||||
} else {
|
||||
qDebug() << "toxav_audio_send_frame error: " << err;
|
||||
}
|
||||
}
|
||||
} while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5);
|
||||
@ -395,21 +372,18 @@ void CoreAV::sendCallVideo(uint32_t callId, std::shared_ptr<VideoFrame> vframe)
|
||||
|
||||
ToxFriendCall& call = calls[callId];
|
||||
|
||||
if (!call.videoEnabled || call.inactive
|
||||
|| !(call.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V))
|
||||
if (!call.videoEnabled || call.inactive || !(call.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V))
|
||||
return;
|
||||
|
||||
if (call.nullVideoBitrate)
|
||||
{
|
||||
qDebug() << "Restarting video stream to friend"<<callId;
|
||||
if (call.nullVideoBitrate) {
|
||||
qDebug() << "Restarting video stream to friend" << callId;
|
||||
toxav_bit_rate_set(toxav, call.callId, -1, VIDEO_DEFAULT_BITRATE, nullptr);
|
||||
call.nullVideoBitrate = false;
|
||||
}
|
||||
|
||||
ToxYUVFrame frame = vframe->toToxYUVFrame();
|
||||
|
||||
if(!frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -418,17 +392,13 @@ void CoreAV::sendCallVideo(uint32_t callId, std::shared_ptr<VideoFrame> vframe)
|
||||
TOXAV_ERR_SEND_FRAME err;
|
||||
int retries = 0;
|
||||
do {
|
||||
if (!toxav_video_send_frame(toxav, callId, frame.width, frame.height,
|
||||
frame.y, frame.u, frame.v, &err))
|
||||
{
|
||||
if (err == TOXAV_ERR_SEND_FRAME_SYNC)
|
||||
{
|
||||
if (!toxav_video_send_frame(toxav, callId, frame.width, frame.height, frame.y, frame.u,
|
||||
frame.v, &err)) {
|
||||
if (err == TOXAV_ERR_SEND_FRAME_SYNC) {
|
||||
++retries;
|
||||
QThread::usleep(500);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "toxav_video_send_frame error: "<<err;
|
||||
} else {
|
||||
qDebug() << "toxav_video_send_frame error: " << err;
|
||||
}
|
||||
}
|
||||
} while (err == TOXAV_ERR_SEND_FRAME_SYNC && retries < 5);
|
||||
@ -442,8 +412,7 @@ void CoreAV::sendCallVideo(uint32_t callId, std::shared_ptr<VideoFrame> vframe)
|
||||
*/
|
||||
void CoreAV::toggleMuteCallInput(const Friend* f)
|
||||
{
|
||||
if (f && calls.contains(f->getFriendId()))
|
||||
{
|
||||
if (f && calls.contains(f->getFriendId())) {
|
||||
ToxCall& call = calls[f->getFriendId()];
|
||||
call.muteMic = !call.muteMic;
|
||||
}
|
||||
@ -455,8 +424,7 @@ void CoreAV::toggleMuteCallInput(const Friend* f)
|
||||
*/
|
||||
void CoreAV::toggleMuteCallOutput(const Friend* f)
|
||||
{
|
||||
if (f && calls.contains(f->getFriendId()))
|
||||
{
|
||||
if (f && calls.contains(f->getFriendId())) {
|
||||
ToxCall& call = calls[f->getFriendId()];
|
||||
call.muteVol = !call.muteVol;
|
||||
}
|
||||
@ -474,18 +442,15 @@ void CoreAV::toggleMuteCallOutput(const Friend* f)
|
||||
* @param[in] sample_rate the audio sample rate
|
||||
* @param[in] core the qTox Core class
|
||||
*/
|
||||
void CoreAV::groupCallCallback(void* tox, int group, int peer,
|
||||
const int16_t* data, unsigned samples,
|
||||
uint8_t channels, unsigned sample_rate,
|
||||
void* core)
|
||||
void CoreAV::groupCallCallback(void* tox, int group, int peer, const int16_t* data,
|
||||
unsigned samples, uint8_t channels, unsigned sample_rate, void* core)
|
||||
{
|
||||
Q_UNUSED(tox);
|
||||
|
||||
Core* c = static_cast<Core*>(core);
|
||||
CoreAV* cav = c->getAv();
|
||||
|
||||
if (!cav->groupCalls.contains(group))
|
||||
{
|
||||
if (!cav->groupCalls.contains(group)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -500,8 +465,7 @@ void CoreAV::groupCallCallback(void* tox, int group, int peer,
|
||||
if (!call.peers[peer])
|
||||
audio.subscribeOutput(call.peers[peer]);
|
||||
|
||||
audio.playAudioBuffer(call.peers[peer], data, samples, channels,
|
||||
sample_rate);
|
||||
audio.playAudioBuffer(call.peers[peer], data, samples, channels, sample_rate);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -509,8 +473,9 @@ void CoreAV::groupCallCallback(void* tox, int group, int peer,
|
||||
* @param group Group Index
|
||||
* @param peer Peer Index
|
||||
*/
|
||||
void CoreAV::invalidateGroupCallPeerSource(int group, int peer) {
|
||||
Audio &audio = Audio::getInstance();
|
||||
void CoreAV::invalidateGroupCallPeerSource(int group, int peer)
|
||||
{
|
||||
Audio& audio = Audio::getInstance();
|
||||
audio.unsubscribeOutput(groupCalls[group].peers[peer]);
|
||||
groupCalls[group].peers[peer] = 0;
|
||||
}
|
||||
@ -520,11 +485,11 @@ void CoreAV::invalidateGroupCallPeerSource(int group, int peer) {
|
||||
* @param friendNum Id of friend in call list.
|
||||
* @return Video surface to show
|
||||
*/
|
||||
VideoSource *CoreAV::getVideoSourceFromCall(int friendNum)
|
||||
VideoSource* CoreAV::getVideoSourceFromCall(int friendNum)
|
||||
{
|
||||
if (!calls.contains(friendNum))
|
||||
{
|
||||
qWarning() << "CoreAV::getVideoSourceFromCall: No such call, did it die before we finished answering?";
|
||||
if (!calls.contains(friendNum)) {
|
||||
qWarning() << "CoreAV::getVideoSourceFromCall: No such call, did it die before we finished "
|
||||
"answering?";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -556,7 +521,8 @@ void CoreAV::leaveGroupCall(int groupId)
|
||||
groupCalls.remove(groupId);
|
||||
}
|
||||
|
||||
bool CoreAV::sendGroupCallAudio(int groupId, const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate)
|
||||
bool CoreAV::sendGroupCallAudio(int groupId, const int16_t* pcm, size_t samples, uint8_t chans,
|
||||
uint32_t rate)
|
||||
{
|
||||
if (!groupCalls.contains(groupId))
|
||||
return false;
|
||||
@ -601,9 +567,7 @@ void CoreAV::muteCallOutput(const Group* g, bool mute)
|
||||
*/
|
||||
bool CoreAV::isGroupCallInputMuted(const Group* g) const
|
||||
{
|
||||
return g && groupCalls.contains(g->getGroupId())
|
||||
? groupCalls[g->getGroupId()].muteMic
|
||||
: false;
|
||||
return g && groupCalls.contains(g->getGroupId()) ? groupCalls[g->getGroupId()].muteMic : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -613,9 +577,7 @@ bool CoreAV::isGroupCallInputMuted(const Group* g) const
|
||||
*/
|
||||
bool CoreAV::isGroupCallOutputMuted(const Group* g) const
|
||||
{
|
||||
return g && groupCalls.contains(g->getGroupId())
|
||||
? groupCalls[g->getGroupId()].muteVol
|
||||
: false;
|
||||
return g && groupCalls.contains(g->getGroupId()) ? groupCalls[g->getGroupId()].muteVol : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -628,12 +590,11 @@ bool CoreAV::isGroupAvEnabled(int groupId) const
|
||||
Tox* tox = Core::getInstance()->tox;
|
||||
TOX_ERR_CONFERENCE_GET_TYPE error;
|
||||
TOX_CONFERENCE_TYPE type = tox_conference_get_type(tox, groupId, &error);
|
||||
switch (error)
|
||||
{
|
||||
switch (error) {
|
||||
case TOX_ERR_CONFERENCE_GET_TYPE_OK:
|
||||
break;
|
||||
case TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND:
|
||||
qCritical() << "Conference not found";
|
||||
qCritical() << "Conference not found";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -649,9 +610,7 @@ bool CoreAV::isGroupAvEnabled(int groupId) const
|
||||
*/
|
||||
bool CoreAV::isCallInputMuted(const Friend* f) const
|
||||
{
|
||||
return f && calls.contains(f->getFriendId())
|
||||
? calls[f->getFriendId()].muteMic
|
||||
: false;
|
||||
return f && calls.contains(f->getFriendId()) ? calls[f->getFriendId()].muteMic : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -661,9 +620,7 @@ bool CoreAV::isCallInputMuted(const Friend* f) const
|
||||
*/
|
||||
bool CoreAV::isCallOutputMuted(const Friend* f) const
|
||||
{
|
||||
return f && calls.contains(f->getFriendId())
|
||||
? calls[f->getFriendId()].muteVol
|
||||
: false;
|
||||
return f && calls.contains(f->getFriendId()) ? calls[f->getFriendId()].muteVol : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -671,13 +628,11 @@ bool CoreAV::isCallOutputMuted(const Friend* f) const
|
||||
*/
|
||||
void CoreAV::invalidateCallSources()
|
||||
{
|
||||
for (ToxGroupCall& call : groupCalls)
|
||||
{
|
||||
for (ToxGroupCall& call : groupCalls) {
|
||||
call.peers.clear();
|
||||
}
|
||||
|
||||
for (ToxFriendCall& call : calls)
|
||||
{
|
||||
for (ToxFriendCall& call : calls) {
|
||||
call.alSource = 0;
|
||||
}
|
||||
}
|
||||
@ -690,8 +645,7 @@ void CoreAV::sendNoVideo()
|
||||
{
|
||||
// We don't change the audio bitrate, but we signal that we're not sending video anymore
|
||||
qDebug() << "CoreAV: Signaling end of video sending";
|
||||
for (ToxFriendCall& call : calls)
|
||||
{
|
||||
for (ToxFriendCall& call : calls) {
|
||||
toxav_bit_rate_set(toxav, call.callId, -1, 0, nullptr);
|
||||
call.nullVideoBitrate = true;
|
||||
}
|
||||
@ -701,12 +655,12 @@ void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool vid
|
||||
{
|
||||
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
||||
|
||||
// Run this slow callback asynchronously on the AV thread to avoid deadlocks with what our caller (toxcore) holds
|
||||
// Run this slow callback asynchronously on the AV thread to avoid deadlocks with what our
|
||||
// caller (toxcore) holds
|
||||
// Also run the code to switch to the CoreAV thread in yet another thread, in case CoreAV
|
||||
// has threadSwitchLock and wants a toxcore lock that our call stack is holding...
|
||||
if (QThread::currentThread() != self->coreavThread.get())
|
||||
{
|
||||
QtConcurrent::run([=](){
|
||||
if (QThread::currentThread() != self->coreavThread.get()) {
|
||||
QtConcurrent::run([=]() {
|
||||
// We assume the original caller doesn't come from the CoreAV thread here
|
||||
while (self->threadSwitchLock.test_and_set(std::memory_order_acquire))
|
||||
QThread::yieldCurrentThread(); // Shouldn't spin for long, we have priority
|
||||
@ -718,11 +672,11 @@ void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool vid
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->calls.contains(friendNum))
|
||||
{
|
||||
if (self->calls.contains(friendNum)) {
|
||||
/// Hanging up from a callback is supposed to be UB,
|
||||
/// but since currently the toxav callbacks are fired from the toxcore thread,
|
||||
/// we'll always reach this point through a non-blocking queud connection, so not in the callback.
|
||||
/// we'll always reach this point through a non-blocking queud connection, so not in the
|
||||
/// callback.
|
||||
qWarning() << QString("Rejecting call invite from %1, we're already in that call!").arg(friendNum);
|
||||
toxav_call_control(toxav, friendNum, TOXAV_CALL_CONTROL_CANCEL, nullptr);
|
||||
return;
|
||||
@ -746,25 +700,24 @@ void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, voi
|
||||
{
|
||||
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
||||
|
||||
// Run this slow callback asynchronously on the AV thread to avoid deadlocks with what our caller (toxcore) holds
|
||||
// Run this slow callback asynchronously on the AV thread to avoid deadlocks with what our
|
||||
// caller (toxcore) holds
|
||||
// Also run the code to switch to the CoreAV thread in yet another thread, in case CoreAV
|
||||
// has threadSwitchLock and wants a toxcore lock that our call stack is holding...
|
||||
if (QThread::currentThread() != self->coreavThread.get())
|
||||
{
|
||||
QtConcurrent::run([=](){
|
||||
if (QThread::currentThread() != self->coreavThread.get()) {
|
||||
QtConcurrent::run([=]() {
|
||||
// We assume the original caller doesn't come from the CoreAV thread here
|
||||
while (self->threadSwitchLock.test_and_set(std::memory_order_acquire))
|
||||
QThread::yieldCurrentThread(); // Shouldn't spin for long, we have priority
|
||||
|
||||
QMetaObject::invokeMethod(self, "stateCallback", Qt::QueuedConnection,
|
||||
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
||||
Q_ARG(uint32_t, state), Q_ARG(void*, vSelf));
|
||||
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
||||
Q_ARG(uint32_t, state), Q_ARG(void*, vSelf));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self->calls.contains(friendNum))
|
||||
{
|
||||
if (!self->calls.contains(friendNum)) {
|
||||
qWarning() << QString("stateCallback called, but call %1 is already dead").arg(friendNum);
|
||||
self->threadSwitchLock.clear(std::memory_order_release);
|
||||
return;
|
||||
@ -772,40 +725,32 @@ void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, voi
|
||||
|
||||
ToxFriendCall& call = self->calls[friendNum];
|
||||
|
||||
if (state & TOXAV_FRIEND_CALL_STATE_ERROR)
|
||||
{
|
||||
qWarning() << "Call with friend"<<friendNum<<"died of unnatural causes!";
|
||||
if (state & TOXAV_FRIEND_CALL_STATE_ERROR) {
|
||||
qWarning() << "Call with friend" << friendNum << "died of unnatural causes!";
|
||||
calls.remove(friendNum);
|
||||
emit self->avEnd(friendNum);
|
||||
}
|
||||
else if (state & TOXAV_FRIEND_CALL_STATE_FINISHED)
|
||||
{
|
||||
qDebug() << "Call with friend"<<friendNum<<"finished quietly";
|
||||
} else if (state & TOXAV_FRIEND_CALL_STATE_FINISHED) {
|
||||
qDebug() << "Call with friend" << friendNum << "finished quietly";
|
||||
calls.remove(friendNum);
|
||||
emit self->avEnd(friendNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// If our state was null, we started the call and were still ringing
|
||||
if (!call.state && state)
|
||||
{
|
||||
if (!call.state && state) {
|
||||
call.stopTimeout();
|
||||
call.inactive = false;
|
||||
emit self->avStart(friendNum, call.videoEnabled);
|
||||
}
|
||||
else if ((call.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)
|
||||
&& !(state & TOXAV_FRIEND_CALL_STATE_SENDING_V))
|
||||
{
|
||||
qDebug() << "Friend"<<friendNum<<"stopped sending video";
|
||||
} else if ((call.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)
|
||||
&& !(state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) {
|
||||
qDebug() << "Friend" << friendNum << "stopped sending video";
|
||||
if (call.videoSource)
|
||||
call.videoSource->stopSource();
|
||||
}
|
||||
else if (!(call.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)
|
||||
&& (state & TOXAV_FRIEND_CALL_STATE_SENDING_V))
|
||||
{
|
||||
// Workaround toxav sometimes firing callbacks for "send last frame" -> "stop sending video"
|
||||
} else if (!(call.state & TOXAV_FRIEND_CALL_STATE_SENDING_V)
|
||||
&& (state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) {
|
||||
// Workaround toxav sometimes firing callbacks for "send last frame" -> "stop sending
|
||||
// video"
|
||||
// out of orders (even though they were sent in order by the other end).
|
||||
// We simply stop the videoSource from emitting anything while the other end says it's not sending
|
||||
// We simply stop the videoSource from emitting anything while the other end says it's
|
||||
// not sending
|
||||
if (call.videoSource)
|
||||
call.videoSource->restartSource();
|
||||
}
|
||||
@ -815,23 +760,25 @@ void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, voi
|
||||
self->threadSwitchLock.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
void CoreAV::bitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t arate, uint32_t vrate, void* vSelf)
|
||||
void CoreAV::bitrateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t arate, uint32_t vrate,
|
||||
void* vSelf)
|
||||
{
|
||||
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
||||
|
||||
// Run this slow path callback asynchronously on the AV thread to avoid deadlocks
|
||||
if (QThread::currentThread() != self->coreavThread.get())
|
||||
{
|
||||
if (QThread::currentThread() != self->coreavThread.get()) {
|
||||
return (void)QMetaObject::invokeMethod(self, "bitrateCallback", Qt::QueuedConnection,
|
||||
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
||||
Q_ARG(uint32_t, arate), Q_ARG(uint32_t, vrate), Q_ARG(void*, vSelf));
|
||||
Q_ARG(ToxAV*, toxav), Q_ARG(uint32_t, friendNum),
|
||||
Q_ARG(uint32_t, arate), Q_ARG(uint32_t, vrate),
|
||||
Q_ARG(void*, vSelf));
|
||||
}
|
||||
|
||||
qDebug() << "Recommended bitrate with"<<friendNum<<" is now "<<arate<<"/"<<vrate<<", ignoring it";
|
||||
qDebug() << "Recommended bitrate with" << friendNum << " is now " << arate << "/" << vrate
|
||||
<< ", ignoring it";
|
||||
}
|
||||
|
||||
void CoreAV::audioFrameCallback(ToxAV *, uint32_t friendNum, const int16_t *pcm,
|
||||
size_t sampleCount, uint8_t channels, uint32_t samplingRate, void* vSelf)
|
||||
void CoreAV::audioFrameCallback(ToxAV*, uint32_t friendNum, const int16_t* pcm, size_t sampleCount,
|
||||
uint8_t channels, uint32_t samplingRate, void* vSelf)
|
||||
{
|
||||
CoreAV* self = static_cast<CoreAV*>(vSelf);
|
||||
if (!self->calls.contains(friendNum))
|
||||
@ -849,9 +796,9 @@ void CoreAV::audioFrameCallback(ToxAV *, uint32_t friendNum, const int16_t *pcm,
|
||||
audio.playAudioBuffer(call.alSource, pcm, sampleCount, channels, samplingRate);
|
||||
}
|
||||
|
||||
void CoreAV::videoFrameCallback(ToxAV *, uint32_t friendNum, uint16_t w, uint16_t h,
|
||||
const uint8_t *y, const uint8_t *u, const uint8_t *v,
|
||||
int32_t ystride, int32_t ustride, int32_t vstride, void *)
|
||||
void CoreAV::videoFrameCallback(ToxAV*, uint32_t friendNum, uint16_t w, uint16_t h,
|
||||
const uint8_t* y, const uint8_t* u, const uint8_t* v,
|
||||
int32_t ystride, int32_t ustride, int32_t vstride, void*)
|
||||
{
|
||||
if (!calls.contains(friendNum))
|
||||
return;
|
||||
|
@ -21,10 +21,10 @@
|
||||
#ifndef COREAV_H
|
||||
#define COREAV_H
|
||||
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include "src/core/toxcall.h"
|
||||
#include <QObject>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <tox/toxav.h>
|
||||
|
||||
class Friend;
|
||||
@ -54,9 +54,11 @@ public:
|
||||
bool isCallActive(const Friend* f) const;
|
||||
bool isCallActive(const Group* g) const;
|
||||
bool isCallVideoEnabled(const Friend* f) const;
|
||||
bool sendCallAudio(uint32_t friendNum, const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate);
|
||||
bool sendCallAudio(uint32_t friendNum, const int16_t* pcm, size_t samples, uint8_t chans,
|
||||
uint32_t rate);
|
||||
void sendCallVideo(uint32_t friendNum, std::shared_ptr<VideoFrame> frame);
|
||||
bool sendGroupCallAudio(int groupNum, const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate);
|
||||
bool sendGroupCallAudio(int groupNum, const int16_t* pcm, size_t samples, uint8_t chans,
|
||||
uint32_t rate);
|
||||
|
||||
VideoSource* getVideoSourceFromCall(int callNumber);
|
||||
void invalidateCallSources();
|
||||
@ -75,14 +77,12 @@ public:
|
||||
void toggleMuteCallInput(const Friend* f);
|
||||
void toggleMuteCallOutput(const Friend* f);
|
||||
|
||||
static void groupCallCallback(void* tox, int group, int peer,
|
||||
const int16_t* data, unsigned samples,
|
||||
uint8_t channels, unsigned sample_rate,
|
||||
void* core);
|
||||
static void groupCallCallback(void* tox, int group, int peer, const int16_t* data, unsigned samples,
|
||||
uint8_t channels, unsigned sample_rate, void* core);
|
||||
static void invalidateGroupCallPeerSource(int group, int peer);
|
||||
|
||||
public slots:
|
||||
bool startCall(uint32_t friendNum, bool video=false);
|
||||
bool startCall(uint32_t friendNum, bool video = false);
|
||||
bool answerCall(uint32_t friendNum);
|
||||
bool cancelCall(uint32_t friendNum);
|
||||
void timeoutCall(uint32_t friendNum);
|
||||
@ -95,17 +95,19 @@ signals:
|
||||
void avEnd(uint32_t friendId);
|
||||
|
||||
private slots:
|
||||
static void callCallback(ToxAV *toxAV, uint32_t friendNum, bool audio, bool video, void* self);
|
||||
static void stateCallback(ToxAV *, uint32_t friendNum, uint32_t state, void* self);
|
||||
static void bitrateCallback(ToxAV *toxAV, uint32_t friendNum, uint32_t arate, uint32_t vrate, void* self);
|
||||
static void callCallback(ToxAV* toxAV, uint32_t friendNum, bool audio, bool video, void* self);
|
||||
static void stateCallback(ToxAV*, uint32_t friendNum, uint32_t state, void* self);
|
||||
static void bitrateCallback(ToxAV* toxAV, uint32_t friendNum, uint32_t arate, uint32_t vrate,
|
||||
void* self);
|
||||
void killTimerFromThread();
|
||||
|
||||
private:
|
||||
void process();
|
||||
static void audioFrameCallback(ToxAV *toxAV, uint32_t friendNum, const int16_t *pcm, size_t sampleCount,
|
||||
uint8_t channels, uint32_t samplingRate, void* self);
|
||||
static void videoFrameCallback(ToxAV *toxAV, uint32_t friendNum, uint16_t w, uint16_t h,
|
||||
const uint8_t *y, const uint8_t *u, const uint8_t *v,
|
||||
static void audioFrameCallback(ToxAV* toxAV, uint32_t friendNum, const int16_t* pcm,
|
||||
size_t sampleCount, uint8_t channels, uint32_t samplingRate,
|
||||
void* self);
|
||||
static void videoFrameCallback(ToxAV* toxAV, uint32_t friendNum, uint16_t w, uint16_t h,
|
||||
const uint8_t* y, const uint8_t* u, const uint8_t* v,
|
||||
int32_t ystride, int32_t ustride, int32_t vstride, void* self);
|
||||
|
||||
private:
|
||||
|
@ -22,12 +22,12 @@
|
||||
#include "corefile.h"
|
||||
#include "corestructs.h"
|
||||
#include "src/core/cstring.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QThread>
|
||||
#include <QDir>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
@ -55,13 +55,10 @@ unsigned CoreFile::corefileIterationInterval()
|
||||
comes to CPU usage – just keep the CPU usage low when there are no file
|
||||
transfers, and speed things up when there is an ongoing file transfer.
|
||||
*/
|
||||
constexpr unsigned fileInterval = 10,
|
||||
idleInterval = 1000;
|
||||
constexpr unsigned fileInterval = 10, idleInterval = 1000;
|
||||
|
||||
for (ToxFile& file : fileMap)
|
||||
{
|
||||
if (file.status == ToxFile::TRANSMITTING)
|
||||
{
|
||||
for (ToxFile& file : fileMap) {
|
||||
if (file.status == ToxFile::TRANSMITTING) {
|
||||
return fileInterval;
|
||||
}
|
||||
}
|
||||
@ -72,10 +69,8 @@ void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& d
|
||||
{
|
||||
QMutexLocker mlocker(&fileSendMutex);
|
||||
|
||||
if (data.isEmpty())
|
||||
{
|
||||
tox_file_send(core->tox, friendId, TOX_FILE_KIND_AVATAR, 0,
|
||||
nullptr, nullptr, 0, nullptr);
|
||||
if (data.isEmpty()) {
|
||||
tox_file_send(core->tox, friendId, TOX_FILE_KIND_AVATAR, 0, nullptr, nullptr, 0, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -86,10 +81,9 @@ void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& d
|
||||
|
||||
TOX_ERR_FILE_SEND error;
|
||||
uint32_t fileNum = tox_file_send(core->tox, friendId, TOX_FILE_KIND_AVATAR, filesize,
|
||||
avatarHash, avatarHash, TOX_HASH_LENGTH, &error);
|
||||
avatarHash, avatarHash, TOX_HASH_LENGTH, &error);
|
||||
|
||||
switch (error)
|
||||
{
|
||||
switch (error) {
|
||||
case TOX_ERR_FILE_SEND_OK:
|
||||
break;
|
||||
case TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED:
|
||||
@ -121,15 +115,15 @@ void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& d
|
||||
addFile(friendId, fileNum, file);
|
||||
}
|
||||
|
||||
void CoreFile::sendFile(Core* core, uint32_t friendId, QString filename, QString filePath, long long filesize)
|
||||
void CoreFile::sendFile(Core* core, uint32_t friendId, QString filename, QString filePath,
|
||||
long long filesize)
|
||||
{
|
||||
QMutexLocker mlocker(&fileSendMutex);
|
||||
|
||||
QByteArray fileName = filename.toUtf8();
|
||||
uint32_t fileNum = tox_file_send(core->tox, friendId, TOX_FILE_KIND_DATA, filesize, nullptr,
|
||||
(uint8_t*)fileName.data(), fileName.size(), nullptr);
|
||||
if (fileNum == std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
(uint8_t*)fileName.data(), fileName.size(), nullptr);
|
||||
if (fileNum == std::numeric_limits<uint32_t>::max()) {
|
||||
qWarning() << "sendFile: Can't create the Tox file sender";
|
||||
emit core->fileSendFailed(friendId, filename);
|
||||
return;
|
||||
@ -140,8 +134,7 @@ void CoreFile::sendFile(Core* core, uint32_t friendId, QString filename, QString
|
||||
file.filesize = filesize;
|
||||
file.resumeFileId.resize(TOX_FILE_ID_LENGTH);
|
||||
tox_file_get_file_id(core->tox, friendId, fileNum, (uint8_t*)file.resumeFileId.data(), nullptr);
|
||||
if (!file.open(false))
|
||||
{
|
||||
if (!file.open(false)) {
|
||||
qWarning() << QString("sendFile: Can't open file, error: %1").arg(file.file->errorString());
|
||||
}
|
||||
|
||||
@ -153,25 +146,19 @@ void CoreFile::sendFile(Core* core, uint32_t friendId, QString filename, QString
|
||||
void CoreFile::pauseResumeFileSend(Core* core, uint32_t friendId, uint32_t fileId)
|
||||
{
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("pauseResumeFileSend: No such file in queue");
|
||||
return;
|
||||
}
|
||||
if (file->status == ToxFile::TRANSMITTING)
|
||||
{
|
||||
if (file->status == ToxFile::TRANSMITTING) {
|
||||
file->status = ToxFile::PAUSED;
|
||||
emit core->fileTransferPaused(*file);
|
||||
tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, nullptr);
|
||||
}
|
||||
else if (file->status == ToxFile::PAUSED)
|
||||
{
|
||||
} else if (file->status == ToxFile::PAUSED) {
|
||||
file->status = ToxFile::TRANSMITTING;
|
||||
emit core->fileTransferAccepted(*file);
|
||||
tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "pauseResumeFileSend: File is stopped";
|
||||
}
|
||||
}
|
||||
@ -179,25 +166,19 @@ void CoreFile::pauseResumeFileSend(Core* core, uint32_t friendId, uint32_t fileI
|
||||
void CoreFile::pauseResumeFileRecv(Core* core, uint32_t friendId, uint32_t fileId)
|
||||
{
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("cancelFileRecv: No such file in queue");
|
||||
return;
|
||||
}
|
||||
if (file->status == ToxFile::TRANSMITTING)
|
||||
{
|
||||
if (file->status == ToxFile::TRANSMITTING) {
|
||||
file->status = ToxFile::PAUSED;
|
||||
emit core->fileTransferPaused(*file);
|
||||
tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, nullptr);
|
||||
}
|
||||
else if (file->status == ToxFile::PAUSED)
|
||||
{
|
||||
} else if (file->status == ToxFile::PAUSED) {
|
||||
file->status = ToxFile::TRANSMITTING;
|
||||
emit core->fileTransferAccepted(*file);
|
||||
tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "pauseResumeFileRecv: File is stopped or broken";
|
||||
}
|
||||
}
|
||||
@ -205,8 +186,7 @@ void CoreFile::pauseResumeFileRecv(Core* core, uint32_t friendId, uint32_t fileI
|
||||
void CoreFile::cancelFileSend(Core* core, uint32_t friendId, uint32_t fileId)
|
||||
{
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("cancelFileSend: No such file in queue");
|
||||
return;
|
||||
}
|
||||
@ -220,8 +200,7 @@ void CoreFile::cancelFileSend(Core* core, uint32_t friendId, uint32_t fileId)
|
||||
void CoreFile::cancelFileRecv(Core* core, uint32_t friendId, uint32_t fileId)
|
||||
{
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("cancelFileRecv: No such file in queue");
|
||||
return;
|
||||
}
|
||||
@ -234,8 +213,7 @@ void CoreFile::cancelFileRecv(Core* core, uint32_t friendId, uint32_t fileId)
|
||||
void CoreFile::rejectFileRecvRequest(Core* core, uint32_t friendId, uint32_t fileId)
|
||||
{
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("rejectFileRecvRequest: No such file in queue");
|
||||
return;
|
||||
}
|
||||
@ -248,14 +226,12 @@ void CoreFile::rejectFileRecvRequest(Core* core, uint32_t friendId, uint32_t fil
|
||||
void CoreFile::acceptFileRecvRequest(Core* core, uint32_t friendId, uint32_t fileId, QString path)
|
||||
{
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("acceptFileRecvRequest: No such file in queue");
|
||||
return;
|
||||
}
|
||||
file->setFilePath(path);
|
||||
if (!file->open(true))
|
||||
{
|
||||
if (!file->open(true)) {
|
||||
qWarning() << "acceptFileRecvRequest: Unable to open file";
|
||||
return;
|
||||
}
|
||||
@ -267,13 +243,11 @@ void CoreFile::acceptFileRecvRequest(Core* core, uint32_t friendId, uint32_t fil
|
||||
ToxFile* CoreFile::findFile(uint32_t friendId, uint32_t fileId)
|
||||
{
|
||||
uint64_t key = getFriendKey(friendId, fileId);
|
||||
if (fileMap.contains(key))
|
||||
{
|
||||
if (fileMap.contains(key)) {
|
||||
return &fileMap[key];
|
||||
}
|
||||
|
||||
qWarning() << "findFile: File transfer with ID" << friendId << ':'
|
||||
<< fileId << "doesn't exist";
|
||||
qWarning() << "findFile: File transfer with ID" << friendId << ':' << fileId << "doesn't exist";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -281,10 +255,9 @@ void CoreFile::addFile(uint32_t friendId, uint32_t fileId, const ToxFile& file)
|
||||
{
|
||||
uint64_t key = getFriendKey(friendId, fileId);
|
||||
|
||||
if (fileMap.contains(key))
|
||||
{
|
||||
qWarning() << "addFile: Overwriting existing file transfer with same ID"
|
||||
<< friendId << ':' << fileId;
|
||||
if (fileMap.contains(key)) {
|
||||
qWarning() << "addFile: Overwriting existing file transfer with same ID" << friendId << ':'
|
||||
<< fileId;
|
||||
}
|
||||
|
||||
fileMap.insert(key, file);
|
||||
@ -293,8 +266,7 @@ void CoreFile::addFile(uint32_t friendId, uint32_t fileId, const ToxFile& file)
|
||||
void CoreFile::removeFile(uint32_t friendId, uint32_t fileId)
|
||||
{
|
||||
uint64_t key = getFriendKey(friendId, fileId);
|
||||
if (!fileMap.contains(key))
|
||||
{
|
||||
if (!fileMap.contains(key)) {
|
||||
qWarning() << "removeFile: No such file in queue";
|
||||
return;
|
||||
}
|
||||
@ -302,52 +274,49 @@ void CoreFile::removeFile(uint32_t friendId, uint32_t fileId)
|
||||
fileMap.remove(key);
|
||||
}
|
||||
|
||||
void CoreFile::onFileReceiveCallback(Tox*, uint32_t friendId, uint32_t fileId,
|
||||
uint32_t kind, uint64_t filesize,
|
||||
const uint8_t* fname, size_t fnameLen,
|
||||
void CoreFile::onFileReceiveCallback(Tox*, uint32_t friendId, uint32_t fileId, uint32_t kind,
|
||||
uint64_t filesize, const uint8_t* fname, size_t fnameLen,
|
||||
void* vCore)
|
||||
{
|
||||
Core* core = static_cast<Core*>(vCore);
|
||||
|
||||
if (kind == TOX_FILE_KIND_AVATAR)
|
||||
{
|
||||
if (kind == TOX_FILE_KIND_AVATAR) {
|
||||
// TODO: port this to ToxPk
|
||||
QString friendAddr = core->getFriendPublicKey(friendId).toString();
|
||||
if (!filesize)
|
||||
{
|
||||
if (!filesize) {
|
||||
qDebug() << QString("Received empty avatar request %1:%2").arg(friendId).arg(fileId);
|
||||
// Avatars of size 0 means explicitely no avatar
|
||||
emit core->friendAvatarRemoved(friendId);
|
||||
core->profile.removeAvatar(friendAddr);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(TOX_HASH_LENGTH <= TOX_FILE_ID_LENGTH, "TOX_HASH_LENGTH > TOX_FILE_ID_LENGTH!");
|
||||
} else {
|
||||
static_assert(TOX_HASH_LENGTH <= TOX_FILE_ID_LENGTH,
|
||||
"TOX_HASH_LENGTH > TOX_FILE_ID_LENGTH!");
|
||||
uint8_t avatarHash[TOX_FILE_ID_LENGTH];
|
||||
tox_file_get_file_id(core->tox, friendId, fileId, avatarHash, nullptr);
|
||||
if (core->profile.getAvatarHash(friendAddr) == QByteArray((char*)avatarHash, TOX_HASH_LENGTH))
|
||||
{
|
||||
if (core->profile.getAvatarHash(friendAddr)
|
||||
== QByteArray((char*)avatarHash, TOX_HASH_LENGTH)) {
|
||||
// If it's an avatar but we already have it cached, cancel
|
||||
qDebug() << QString("Received avatar request %1:%2, reject, since we have it in cache.").arg(friendId).arg(fileId);
|
||||
qDebug() << QString(
|
||||
"Received avatar request %1:%2, reject, since we have it in cache.")
|
||||
.arg(friendId)
|
||||
.arg(fileId);
|
||||
tox_file_control(core->tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// It's an avatar and we don't have it, autoaccept the transfer
|
||||
qDebug() << QString("Received avatar request %1:%2, accept, since we don't have it in cache.").arg(friendId).arg(fileId);
|
||||
qDebug() << QString("Received avatar request %1:%2, accept, since we don't have it "
|
||||
"in cache.")
|
||||
.arg(friendId)
|
||||
.arg(fileId);
|
||||
tox_file_control(core->tox, friendId, fileId, TOX_FILE_CONTROL_RESUME, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << QString("Received file request %1:%2 kind %3")
|
||||
.arg(friendId).arg(fileId).arg(kind);
|
||||
} else {
|
||||
qDebug() << QString("Received file request %1:%2 kind %3").arg(friendId).arg(fileId).arg(kind);
|
||||
}
|
||||
|
||||
ToxFile file{fileId, friendId, QByteArray((char*)fname,fnameLen), "", ToxFile::RECEIVING};
|
||||
ToxFile file{fileId, friendId, QByteArray((char*)fname, fnameLen), "", ToxFile::RECEIVING};
|
||||
file.filesize = filesize;
|
||||
file.fileKind = kind;
|
||||
file.resumeFileId.resize(TOX_FILE_ID_LENGTH);
|
||||
@ -357,59 +326,48 @@ void CoreFile::onFileReceiveCallback(Tox*, uint32_t friendId, uint32_t fileId,
|
||||
emit core->fileReceiveRequested(file);
|
||||
}
|
||||
void CoreFile::onFileControlCallback(Tox*, uint32_t friendId, uint32_t fileId,
|
||||
TOX_FILE_CONTROL control, void *core)
|
||||
TOX_FILE_CONTROL control, void* core)
|
||||
{
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("onFileControlCallback: No such file in queue");
|
||||
return;
|
||||
}
|
||||
|
||||
if (control == TOX_FILE_CONTROL_CANCEL)
|
||||
{
|
||||
if (control == TOX_FILE_CONTROL_CANCEL) {
|
||||
if (file->fileKind != TOX_FILE_KIND_AVATAR)
|
||||
qDebug() << "File tranfer"<<friendId<<":"<<fileId<<"cancelled by friend";
|
||||
qDebug() << "File tranfer" << friendId << ":" << fileId << "cancelled by friend";
|
||||
emit static_cast<Core*>(core)->fileTransferCancelled(*file);
|
||||
removeFile(friendId, fileId);
|
||||
}
|
||||
else if (control == TOX_FILE_CONTROL_PAUSE)
|
||||
{
|
||||
qDebug() << "onFileControlCallback: Received pause for file "<<friendId<<":"<<fileId;
|
||||
} else if (control == TOX_FILE_CONTROL_PAUSE) {
|
||||
qDebug() << "onFileControlCallback: Received pause for file " << friendId << ":" << fileId;
|
||||
file->status = ToxFile::PAUSED;
|
||||
emit static_cast<Core*>(core)->fileTransferRemotePausedUnpaused(*file, true);
|
||||
}
|
||||
else if (control == TOX_FILE_CONTROL_RESUME)
|
||||
{
|
||||
} else if (control == TOX_FILE_CONTROL_RESUME) {
|
||||
if (file->direction == ToxFile::SENDING && file->fileKind == TOX_FILE_KIND_AVATAR)
|
||||
qDebug() << "Avatar transfer"<<fileId<<"to friend"<<friendId<<"accepted";
|
||||
qDebug() << "Avatar transfer" << fileId << "to friend" << friendId << "accepted";
|
||||
else
|
||||
qDebug() << "onFileControlCallback: Received resume for file "<<friendId<<":"<<fileId;
|
||||
qDebug() << "onFileControlCallback: Received resume for file " << friendId << ":" << fileId;
|
||||
file->status = ToxFile::TRANSMITTING;
|
||||
emit static_cast<Core*>(core)->fileTransferRemotePausedUnpaused(*file, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Unhandled file control "<<control<<" for file "<<friendId<<':'<<fileId;
|
||||
} else {
|
||||
qWarning() << "Unhandled file control " << control << " for file " << friendId << ':' << fileId;
|
||||
}
|
||||
}
|
||||
|
||||
void CoreFile::onFileDataCallback(Tox *tox, uint32_t friendId, uint32_t fileId,
|
||||
uint64_t pos, size_t length, void* core)
|
||||
void CoreFile::onFileDataCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint64_t pos,
|
||||
size_t length, void* core)
|
||||
{
|
||||
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("onFileDataCallback: No such file in queue");
|
||||
return;
|
||||
}
|
||||
|
||||
// If we reached EOF, ack and cleanup the transfer
|
||||
if (!length)
|
||||
{
|
||||
if (file->fileKind != TOX_FILE_KIND_AVATAR)
|
||||
{
|
||||
if (!length) {
|
||||
if (file->fileKind != TOX_FILE_KIND_AVATAR) {
|
||||
emit static_cast<Core*>(core)->fileTransferFinished(*file);
|
||||
emit static_cast<Core*>(core)->fileUploadFinished(file->filePath);
|
||||
}
|
||||
@ -420,18 +378,14 @@ void CoreFile::onFileDataCallback(Tox *tox, uint32_t friendId, uint32_t fileId,
|
||||
unique_ptr<uint8_t[]> data(new uint8_t[length]);
|
||||
int64_t nread;
|
||||
|
||||
if (file->fileKind == TOX_FILE_KIND_AVATAR)
|
||||
{
|
||||
if (file->fileKind == TOX_FILE_KIND_AVATAR) {
|
||||
QByteArray chunk = file->avatarData.mid(pos, length);
|
||||
nread = chunk.size();
|
||||
memcpy(data.get(), chunk.data(), nread);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
file->file->seek(pos);
|
||||
nread = file->file->read((char*)data.get(), length);
|
||||
if (nread <= 0)
|
||||
{
|
||||
if (nread <= 0) {
|
||||
qWarning("onFileDataCallback: Failed to read from file");
|
||||
emit static_cast<Core*>(core)->fileTransferCancelled(*file);
|
||||
tox_file_send_chunk(tox, friendId, fileId, pos, nullptr, 0, nullptr);
|
||||
@ -441,8 +395,7 @@ void CoreFile::onFileDataCallback(Tox *tox, uint32_t friendId, uint32_t fileId,
|
||||
file->bytesSent += length;
|
||||
}
|
||||
|
||||
if (!tox_file_send_chunk(tox, friendId, fileId, pos, data.get(), nread, nullptr))
|
||||
{
|
||||
if (!tox_file_send_chunk(tox, friendId, fileId, pos, data.get(), nread, nullptr)) {
|
||||
qWarning("onFileDataCallback: Failed to send data chunk");
|
||||
return;
|
||||
}
|
||||
@ -450,22 +403,18 @@ void CoreFile::onFileDataCallback(Tox *tox, uint32_t friendId, uint32_t fileId,
|
||||
emit static_cast<Core*>(core)->fileTransferInfo(*file);
|
||||
}
|
||||
|
||||
void CoreFile::onFileRecvChunkCallback(Tox *tox, uint32_t friendId,
|
||||
uint32_t fileId, uint64_t position,
|
||||
const uint8_t* data, size_t length,
|
||||
void* vCore)
|
||||
void CoreFile::onFileRecvChunkCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint64_t position,
|
||||
const uint8_t* data, size_t length, void* vCore)
|
||||
{
|
||||
Core* core = static_cast<Core*>(vCore);
|
||||
ToxFile* file = findFile(friendId, fileId);
|
||||
if (!file)
|
||||
{
|
||||
if (!file) {
|
||||
qWarning("onFileRecvChunkCallback: No such file in queue");
|
||||
tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (file->bytesSent != position)
|
||||
{
|
||||
if (file->bytesSent != position) {
|
||||
qWarning("onFileRecvChunkCallback: Received a chunk out-of-order, aborting transfer");
|
||||
if (file->fileKind != TOX_FILE_KIND_AVATAR)
|
||||
emit core->fileTransferCancelled(*file);
|
||||
@ -474,21 +423,17 @@ void CoreFile::onFileRecvChunkCallback(Tox *tox, uint32_t friendId,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!length)
|
||||
{
|
||||
if (file->fileKind == TOX_FILE_KIND_AVATAR)
|
||||
{
|
||||
if (!length) {
|
||||
if (file->fileKind == TOX_FILE_KIND_AVATAR) {
|
||||
QPixmap pic;
|
||||
pic.loadFromData(file->avatarData);
|
||||
if (!pic.isNull())
|
||||
{
|
||||
qDebug() << "Got"<<file->avatarData.size()<<"bytes of avatar data from" <<friendId;
|
||||
core->profile.saveAvatar(file->avatarData, core->getFriendPublicKey(friendId).toString());
|
||||
if (!pic.isNull()) {
|
||||
qDebug() << "Got" << file->avatarData.size() << "bytes of avatar data from" << friendId;
|
||||
core->profile.saveAvatar(file->avatarData,
|
||||
core->getFriendPublicKey(friendId).toString());
|
||||
emit core->friendAvatarChanged(friendId, pic);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
emit core->fileTransferFinished(*file);
|
||||
emit core->fileDownloadFinished(file->filePath);
|
||||
}
|
||||
@ -499,7 +444,7 @@ void CoreFile::onFileRecvChunkCallback(Tox *tox, uint32_t friendId,
|
||||
if (file->fileKind == TOX_FILE_KIND_AVATAR)
|
||||
file->avatarData.append((char*)data, length);
|
||||
else
|
||||
file->file->write((char*)data,length);
|
||||
file->file->write((char*)data, length);
|
||||
file->bytesSent += length;
|
||||
|
||||
if (file->fileKind != TOX_FILE_KIND_AVATAR)
|
||||
@ -513,11 +458,11 @@ void CoreFile::onConnectionStatusChanged(Core* core, uint32_t friendId, bool onl
|
||||
// - Start a new file transfer with the same 32byte file ID with toxcore
|
||||
// - Seek to the correct position again
|
||||
// - Update the fileNum in our ToxFile
|
||||
// - Update the users of our signals to check the 32byte tox file ID, not the uint32_t file_num (fileId)
|
||||
// - Update the users of our signals to check the 32byte tox file ID, not the uint32_t file_num
|
||||
// (fileId)
|
||||
ToxFile::FileStatus status = online ? ToxFile::TRANSMITTING : ToxFile::BROKEN;
|
||||
for (uint64_t key : fileMap.keys())
|
||||
{
|
||||
if (key>>32 != friendId)
|
||||
for (uint64_t key : fileMap.keys()) {
|
||||
if (key >> 32 != friendId)
|
||||
continue;
|
||||
fileMap[key].status = status;
|
||||
emit core->fileTransferBrokenUnbroken(fileMap[key], !online);
|
||||
|
@ -21,16 +21,16 @@
|
||||
#ifndef COREFILE_H
|
||||
#define COREFILE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <tox/tox.h>
|
||||
|
||||
#include "corestructs.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QMutex>
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
|
||||
struct Tox;
|
||||
class Core;
|
||||
@ -40,38 +40,20 @@ class CoreFile
|
||||
friend class Core;
|
||||
|
||||
private:
|
||||
CoreFile()=delete;
|
||||
CoreFile() = delete;
|
||||
|
||||
// Internal file sending APIs, used by Core. Public API in core.h
|
||||
private:
|
||||
static void sendFile(Core *core,
|
||||
uint32_t friendId,
|
||||
QString filename,
|
||||
QString filePath,
|
||||
static void sendFile(Core* core, uint32_t friendId, QString filename, QString filePath,
|
||||
long long filesize);
|
||||
static void sendAvatarFile(Core* core,
|
||||
uint32_t friendId,
|
||||
const QByteArray& data);
|
||||
static void pauseResumeFileSend(Core* core,
|
||||
uint32_t friendId,
|
||||
uint32_t fileId);
|
||||
static void pauseResumeFileRecv(Core* core,
|
||||
uint32_t friendId,
|
||||
uint32_t fileId);
|
||||
static void cancelFileSend(Core* core,
|
||||
uint32_t friendId,
|
||||
uint32_t fileId);
|
||||
static void cancelFileRecv(Core* core,
|
||||
uint32_t friendId,
|
||||
uint32_t fileId);
|
||||
static void rejectFileRecvRequest(Core* core,
|
||||
uint32_t friendId,
|
||||
uint32_t fileId);
|
||||
static void acceptFileRecvRequest(Core* core,
|
||||
uint32_t friendId,
|
||||
uint32_t fileId,
|
||||
QString path);
|
||||
static ToxFile *findFile(uint32_t friendId, uint32_t fileId);
|
||||
static void sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& data);
|
||||
static void pauseResumeFileSend(Core* core, uint32_t friendId, uint32_t fileId);
|
||||
static void pauseResumeFileRecv(Core* core, uint32_t friendId, uint32_t fileId);
|
||||
static void cancelFileSend(Core* core, uint32_t friendId, uint32_t fileId);
|
||||
static void cancelFileRecv(Core* core, uint32_t friendId, uint32_t fileId);
|
||||
static void rejectFileRecvRequest(Core* core, uint32_t friendId, uint32_t fileId);
|
||||
static void acceptFileRecvRequest(Core* core, uint32_t friendId, uint32_t fileId, QString path);
|
||||
static ToxFile* findFile(uint32_t friendId, uint32_t fileId);
|
||||
static void addFile(uint32_t friendId, uint32_t fileId, const ToxFile& file);
|
||||
static void removeFile(uint32_t friendId, uint32_t fileId);
|
||||
static unsigned corefileIterationInterval();
|
||||
@ -81,28 +63,16 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
static void onFileReceiveCallback(Tox*,
|
||||
uint32_t friendId,
|
||||
uint32_t fileId,
|
||||
uint32_t kind,
|
||||
uint64_t filesize,
|
||||
const uint8_t* fname,
|
||||
size_t fnameLen,
|
||||
void *vCore);
|
||||
static void onFileControlCallback(Tox *tox, uint32_t friendId, uint32_t fileId,
|
||||
TOX_FILE_CONTROL control, void *core);
|
||||
static void onFileDataCallback(Tox *tox, uint32_t friendId, uint32_t fileId,
|
||||
uint64_t pos, size_t length, void *core);
|
||||
static void onFileRecvChunkCallback(Tox *tox,
|
||||
uint32_t friendId,
|
||||
uint32_t fileId,
|
||||
uint64_t position,
|
||||
const uint8_t* data,
|
||||
size_t length,
|
||||
void *vCore);
|
||||
static void onConnectionStatusChanged(Core* core,
|
||||
uint32_t friendId,
|
||||
bool online);
|
||||
static void onFileReceiveCallback(Tox*, uint32_t friendId, uint32_t fileId, uint32_t kind,
|
||||
uint64_t filesize, const uint8_t* fname, size_t fnameLen,
|
||||
void* vCore);
|
||||
static void onFileControlCallback(Tox* tox, uint32_t friendId, uint32_t fileId,
|
||||
TOX_FILE_CONTROL control, void* core);
|
||||
static void onFileDataCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint64_t pos,
|
||||
size_t length, void* core);
|
||||
static void onFileRecvChunkCallback(Tox* tox, uint32_t friendId, uint32_t fileId, uint64_t position,
|
||||
const uint8_t* data, size_t length, void* vCore);
|
||||
static void onConnectionStatusChanged(Core* core, uint32_t friendId, bool online);
|
||||
|
||||
private:
|
||||
static QMutex fileSendMutex;
|
||||
|
@ -1,10 +1,10 @@
|
||||
#include "src/core/corestructs.h"
|
||||
#include "src/core/core.h"
|
||||
#include <tox/tox.h>
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <tox/tox.h>
|
||||
|
||||
#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE
|
||||
#define TOX_HEX_ID_LENGTH 2 * TOX_ADDRESS_SIZE
|
||||
|
||||
/**
|
||||
* @file corestructs.h
|
||||
@ -23,9 +23,8 @@
|
||||
*/
|
||||
bool DhtServer::operator==(const DhtServer& other) const
|
||||
{
|
||||
return this == &other ||
|
||||
(port == other.port && address == other.address &&
|
||||
userId == other.userId && name == other.name);
|
||||
return this == &other || (port == other.port && address == other.address
|
||||
&& userId == other.userId && name == other.name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,26 +40,35 @@ bool DhtServer::operator!=(const DhtServer& other) const
|
||||
/**
|
||||
* @brief ToxFile constructor
|
||||
*/
|
||||
ToxFile::ToxFile(uint32_t fileNum, uint32_t friendId, QByteArray filename, QString filePath, FileDirection Direction)
|
||||
: fileKind{TOX_FILE_KIND_DATA}, fileNum(fileNum), friendId(friendId), fileName{filename},
|
||||
filePath{filePath}, file{new QFile(filePath)}, bytesSent{0}, filesize{0},
|
||||
status{STOPPED}, direction{Direction}
|
||||
ToxFile::ToxFile(uint32_t fileNum, uint32_t friendId, QByteArray filename, QString filePath,
|
||||
FileDirection Direction)
|
||||
: fileKind{TOX_FILE_KIND_DATA}
|
||||
, fileNum(fileNum)
|
||||
, friendId(friendId)
|
||||
, fileName{filename}
|
||||
, filePath{filePath}
|
||||
, file{new QFile(filePath)}
|
||||
, bytesSent{0}
|
||||
, filesize{0}
|
||||
, status{STOPPED}
|
||||
, direction{Direction}
|
||||
{
|
||||
}
|
||||
|
||||
bool ToxFile::operator==(const ToxFile &other) const
|
||||
bool ToxFile::operator==(const ToxFile& other) const
|
||||
{
|
||||
return (fileNum == other.fileNum) && (friendId == other.friendId) && (direction == other.direction);
|
||||
return (fileNum == other.fileNum) && (friendId == other.friendId)
|
||||
&& (direction == other.direction);
|
||||
}
|
||||
|
||||
bool ToxFile::operator!=(const ToxFile &other) const
|
||||
bool ToxFile::operator!=(const ToxFile& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
void ToxFile::setFilePath(QString path)
|
||||
{
|
||||
filePath=path;
|
||||
filePath = path;
|
||||
file->setFileName(path);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,13 @@
|
||||
class QFile;
|
||||
class QTimer;
|
||||
|
||||
enum class Status : int {Online = 0, Away, Busy, Offline};
|
||||
enum class Status : int
|
||||
{
|
||||
Online = 0,
|
||||
Away,
|
||||
Busy,
|
||||
Offline
|
||||
};
|
||||
|
||||
struct DhtServer
|
||||
{
|
||||
@ -37,8 +43,11 @@ struct ToxFile
|
||||
};
|
||||
|
||||
ToxFile() = default;
|
||||
ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString filePath, FileDirection Direction);
|
||||
~ToxFile(){}
|
||||
ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString filePath,
|
||||
FileDirection Direction);
|
||||
~ToxFile()
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const ToxFile& other) const;
|
||||
bool operator!=(const ToxFile& other) const;
|
||||
|
@ -21,8 +21,8 @@
|
||||
#include "cstring.h"
|
||||
#include <QString>
|
||||
|
||||
CString::CString(const QString& string) :
|
||||
CString(string.toUtf8())
|
||||
CString::CString(const QString& string)
|
||||
: CString(string.toUtf8())
|
||||
{
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ CString::CString(const QByteArray& ba_string)
|
||||
memcpy(cString, reinterpret_cast<const uint8_t*>(ba_string.data()), cStringSize);
|
||||
}
|
||||
|
||||
CString::CString(const CString &cstr)
|
||||
CString::CString(const CString& cstr)
|
||||
{
|
||||
cStringSize = cstr.cStringSize;
|
||||
cString = new uint8_t[cStringSize]();
|
||||
|
@ -1,8 +1,8 @@
|
||||
#ifndef INDEXEDLIST_H
|
||||
#define INDEXEDLIST_H
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
template <typename T>
|
||||
class IndexedList
|
||||
@ -19,28 +19,22 @@ public:
|
||||
template <typename cmp_type>
|
||||
bool contains(cmp_type i)
|
||||
{
|
||||
return std::find_if(begin(), end(), [i](T& t)
|
||||
{
|
||||
return static_cast<cmp_type>(t) == i;
|
||||
}) != end();
|
||||
return std::find_if(begin(), end(), [i](T& t) { return static_cast<cmp_type>(t) == i; })
|
||||
!= end();
|
||||
}
|
||||
|
||||
template <typename cmp_type>
|
||||
void remove(cmp_type i)
|
||||
{
|
||||
v.erase(std::remove_if(begin(), end(), [i](T& t)
|
||||
{
|
||||
return static_cast<cmp_type>(t) == i;
|
||||
}), end());
|
||||
v.erase(std::remove_if(begin(), end(), [i](T& t) { return static_cast<cmp_type>(t) == i; }),
|
||||
end());
|
||||
}
|
||||
|
||||
template <typename cmp_type>
|
||||
T& operator[](cmp_type i)
|
||||
{
|
||||
iterator it = std::find_if(begin(), end(), [i](T& t)
|
||||
{
|
||||
return static_cast<cmp_type>(t) == i;
|
||||
});
|
||||
iterator it =
|
||||
std::find_if(begin(), end(), [i](T& t) { return static_cast<cmp_type>(t) == i; });
|
||||
|
||||
if (it == end())
|
||||
it = insert({});
|
||||
|
@ -53,8 +53,7 @@ void RecursiveSignalBlocker::recursiveBlock(QObject* object)
|
||||
{
|
||||
mBlockers << new QSignalBlocker(object);
|
||||
|
||||
for (QObject* child : object->children())
|
||||
{
|
||||
for (QObject* child : object->children()) {
|
||||
recursiveBlock(child);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "src/audio/audio.h"
|
||||
#include "src/core/toxcall.h"
|
||||
#include "src/audio/audio.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/video/camerasource.h"
|
||||
@ -12,7 +12,8 @@
|
||||
* @brief Could be a friendNum or groupNum, must uniquely identify the call. Do not modify!
|
||||
*
|
||||
* @var bool ToxCall::inactive
|
||||
* @brief True while we're not participating. (stopped group call, ringing but hasn't started yet, ...)
|
||||
* @brief True while we're not participating. (stopped group call, ringing but hasn't started yet,
|
||||
* ...)
|
||||
*
|
||||
* @var bool ToxFriendCall::videoEnabled
|
||||
* @brief True if our user asked for a video call, sending and recieving.
|
||||
@ -30,17 +31,23 @@
|
||||
using namespace std;
|
||||
|
||||
ToxCall::ToxCall(uint32_t CallId)
|
||||
: callId{CallId}, alSource{0},
|
||||
inactive{true}, muteMic{false}, muteVol{false}
|
||||
: callId{CallId}
|
||||
, alSource{0}
|
||||
, inactive{true}
|
||||
, muteMic{false}
|
||||
, muteVol{false}
|
||||
{
|
||||
Audio& audio = Audio::getInstance();
|
||||
audio.subscribeInput();
|
||||
audio.subscribeOutput(alSource);
|
||||
}
|
||||
|
||||
ToxCall::ToxCall(ToxCall&& other) noexcept
|
||||
: audioInConn{other.audioInConn}, callId{other.callId}, alSource{other.alSource},
|
||||
inactive{other.inactive}, muteMic{other.muteMic}, muteVol{other.muteVol}
|
||||
ToxCall::ToxCall(ToxCall&& other) noexcept : audioInConn{other.audioInConn},
|
||||
callId{other.callId},
|
||||
alSource{other.alSource},
|
||||
inactive{other.inactive},
|
||||
muteMic{other.muteMic},
|
||||
muteVol{other.muteVol}
|
||||
{
|
||||
other.audioInConn = QMetaObject::Connection();
|
||||
other.callId = numeric_limits<decltype(callId)>::max();
|
||||
@ -81,15 +88,13 @@ ToxCall& ToxCall::operator=(ToxCall&& other) noexcept
|
||||
|
||||
void ToxFriendCall::startTimeout()
|
||||
{
|
||||
if (!timeoutTimer)
|
||||
{
|
||||
if (!timeoutTimer) {
|
||||
timeoutTimer = new QTimer();
|
||||
// We might move, so we need copies of members. CoreAV won't move while we're alive
|
||||
CoreAV* avCopy = av;
|
||||
auto callIdCopy = callId;
|
||||
QObject::connect(timeoutTimer, &QTimer::timeout, [avCopy, callIdCopy](){
|
||||
avCopy->timeoutCall(callIdCopy);
|
||||
});
|
||||
QObject::connect(timeoutTimer, &QTimer::timeout,
|
||||
[avCopy, callIdCopy]() { avCopy->timeoutCall(callIdCopy); });
|
||||
}
|
||||
|
||||
if (!timeoutTimer->isActive())
|
||||
@ -107,19 +112,21 @@ void ToxFriendCall::stopTimeout()
|
||||
}
|
||||
|
||||
ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av)
|
||||
: ToxCall(FriendNum),
|
||||
videoEnabled{VideoEnabled}, nullVideoBitrate{false}, videoSource{nullptr},
|
||||
state{static_cast<TOXAV_FRIEND_CALL_STATE>(0)},
|
||||
av{&av}, timeoutTimer{nullptr}
|
||||
: ToxCall(FriendNum)
|
||||
, videoEnabled{VideoEnabled}
|
||||
, nullVideoBitrate{false}
|
||||
, videoSource{nullptr}
|
||||
, state{static_cast<TOXAV_FRIEND_CALL_STATE>(0)}
|
||||
, av{&av}
|
||||
, timeoutTimer{nullptr}
|
||||
{
|
||||
audioInConn = QObject::connect(&Audio::getInstance(), &Audio::frameAvailable,
|
||||
[&av,FriendNum](const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate)
|
||||
{
|
||||
av.sendCallAudio(FriendNum, pcm, samples, chans, rate);
|
||||
});
|
||||
[&av, FriendNum](const int16_t* pcm, size_t samples,
|
||||
uint8_t chans, uint32_t rate) {
|
||||
av.sendCallAudio(FriendNum, pcm, samples, chans, rate);
|
||||
});
|
||||
|
||||
if (videoEnabled)
|
||||
{
|
||||
if (videoEnabled) {
|
||||
videoSource = new CoreVideoSource;
|
||||
CameraSource& source = CameraSource::getInstance();
|
||||
|
||||
@ -127,15 +134,20 @@ ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av)
|
||||
source.open();
|
||||
source.subscribe();
|
||||
QObject::connect(&source, &VideoSource::frameAvailable,
|
||||
[FriendNum,&av](shared_ptr<VideoFrame> frame){av.sendCallVideo(FriendNum,frame);});
|
||||
[FriendNum, &av](shared_ptr<VideoFrame> frame) {
|
||||
av.sendCallVideo(FriendNum, frame);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ToxFriendCall::ToxFriendCall(ToxFriendCall&& other) noexcept
|
||||
: ToxCall(move(other)),
|
||||
videoEnabled{other.videoEnabled}, nullVideoBitrate{other.nullVideoBitrate},
|
||||
videoSource{other.videoSource}, state{other.state},
|
||||
av{other.av}, timeoutTimer{other.timeoutTimer}
|
||||
videoEnabled{other.videoEnabled},
|
||||
nullVideoBitrate{other.nullVideoBitrate},
|
||||
videoSource{other.videoSource},
|
||||
state{other.state},
|
||||
av{other.av},
|
||||
timeoutTimer{other.timeoutTimer}
|
||||
{
|
||||
other.videoEnabled = false;
|
||||
other.videoSource = nullptr;
|
||||
@ -147,14 +159,14 @@ ToxFriendCall::~ToxFriendCall()
|
||||
if (timeoutTimer)
|
||||
delete timeoutTimer;
|
||||
|
||||
if (videoEnabled)
|
||||
{
|
||||
if (videoEnabled) {
|
||||
// This destructor could be running in a toxav callback while holding toxav locks.
|
||||
// If the CameraSource thread calls toxav *_send_frame, we might deadlock the toxav and CameraSource locks,
|
||||
// so we unsuscribe asynchronously, it's fine if the webcam takes a couple milliseconds more to poweroff.
|
||||
QtConcurrent::run([](){CameraSource::getInstance().unsubscribe();});
|
||||
if (videoSource)
|
||||
{
|
||||
// If the CameraSource thread calls toxav *_send_frame, we might deadlock the toxav and
|
||||
// CameraSource locks,
|
||||
// so we unsuscribe asynchronously, it's fine if the webcam takes a couple milliseconds more
|
||||
// to poweroff.
|
||||
QtConcurrent::run([]() { CameraSource::getInstance().unsubscribe(); });
|
||||
if (videoSource) {
|
||||
videoSource->setDeleteOnClose(true);
|
||||
videoSource = nullptr;
|
||||
}
|
||||
@ -163,7 +175,7 @@ ToxFriendCall::~ToxFriendCall()
|
||||
|
||||
ToxFriendCall& ToxFriendCall::operator=(ToxFriendCall&& other) noexcept
|
||||
{
|
||||
ToxCall::operator =(move(other));
|
||||
ToxCall::operator=(move(other));
|
||||
videoEnabled = other.videoEnabled;
|
||||
other.videoEnabled = false;
|
||||
videoSource = other.videoSource;
|
||||
@ -177,21 +189,21 @@ ToxFriendCall& ToxFriendCall::operator=(ToxFriendCall&& other) noexcept
|
||||
return *this;
|
||||
}
|
||||
|
||||
ToxGroupCall::ToxGroupCall(int GroupNum, CoreAV &av)
|
||||
ToxGroupCall::ToxGroupCall(int GroupNum, CoreAV& av)
|
||||
: ToxCall(static_cast<decltype(callId)>(GroupNum))
|
||||
{
|
||||
static_assert(numeric_limits<decltype(callId)>::max() >= numeric_limits<decltype(GroupNum)>::max(),
|
||||
"The callId must be able to represent any group number, change its type if needed");
|
||||
static_assert(
|
||||
numeric_limits<decltype(callId)>::max() >= numeric_limits<decltype(GroupNum)>::max(),
|
||||
"The callId must be able to represent any group number, change its type if needed");
|
||||
|
||||
audioInConn = QObject::connect(&Audio::getInstance(), &Audio::frameAvailable,
|
||||
[&av,GroupNum](const int16_t *pcm, size_t samples, uint8_t chans, uint32_t rate)
|
||||
{
|
||||
av.sendGroupCallAudio(GroupNum, pcm, samples, chans, rate);
|
||||
});
|
||||
[&av, GroupNum](const int16_t* pcm, size_t samples,
|
||||
uint8_t chans, uint32_t rate) {
|
||||
av.sendGroupCallAudio(GroupNum, pcm, samples, chans, rate);
|
||||
});
|
||||
}
|
||||
|
||||
ToxGroupCall::ToxGroupCall(ToxGroupCall&& other) noexcept
|
||||
: ToxCall(move(other))
|
||||
ToxGroupCall::ToxGroupCall(ToxGroupCall&& other) noexcept : ToxCall(move(other))
|
||||
{
|
||||
}
|
||||
|
||||
@ -199,15 +211,14 @@ ToxGroupCall::~ToxGroupCall()
|
||||
{
|
||||
Audio& audio = Audio::getInstance();
|
||||
|
||||
for(quint32 v : peers)
|
||||
{
|
||||
for (quint32 v : peers) {
|
||||
audio.unsubscribeOutput(v);
|
||||
}
|
||||
}
|
||||
|
||||
ToxGroupCall &ToxGroupCall::operator=(ToxGroupCall &&other) noexcept
|
||||
ToxGroupCall& ToxGroupCall::operator=(ToxGroupCall&& other) noexcept
|
||||
{
|
||||
ToxCall::operator =(move(other));
|
||||
ToxCall::operator=(move(other));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
#ifndef TOXCALL_H
|
||||
#define TOXCALL_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <QtGlobal>
|
||||
#include <QMetaObject>
|
||||
#include <QMap>
|
||||
#include <QMetaObject>
|
||||
#include <QtGlobal>
|
||||
#include <cstdint>
|
||||
|
||||
#include "src/core/indexedlist.h"
|
||||
|
||||
@ -18,19 +18,23 @@ class CoreAV;
|
||||
struct ToxCall
|
||||
{
|
||||
protected:
|
||||
ToxCall() = default;
|
||||
explicit ToxCall(uint32_t CallId);
|
||||
~ToxCall();
|
||||
public:
|
||||
ToxCall(const ToxCall& other) = delete;
|
||||
ToxCall(ToxCall&& other) noexcept;
|
||||
ToxCall() = default;
|
||||
explicit ToxCall(uint32_t CallId);
|
||||
~ToxCall();
|
||||
|
||||
inline operator int() {return callId;}
|
||||
ToxCall& operator=(const ToxCall& other) = delete;
|
||||
ToxCall& operator=(ToxCall&& other) noexcept;
|
||||
public:
|
||||
ToxCall(const ToxCall& other) = delete;
|
||||
ToxCall(ToxCall&& other) noexcept;
|
||||
|
||||
inline operator int()
|
||||
{
|
||||
return callId;
|
||||
}
|
||||
ToxCall& operator=(const ToxCall& other) = delete;
|
||||
ToxCall& operator=(ToxCall&& other) noexcept;
|
||||
|
||||
protected:
|
||||
QMetaObject::Connection audioInConn;
|
||||
QMetaObject::Connection audioInConn;
|
||||
|
||||
public:
|
||||
uint32_t callId;
|
||||
@ -80,4 +84,3 @@ struct ToxGroupCall : public ToxCall
|
||||
};
|
||||
|
||||
#endif // TOXCALL_H
|
||||
|
||||
|
@ -20,10 +20,10 @@
|
||||
#include "toxencrypt.h"
|
||||
#include <tox/toxencryptsave.h>
|
||||
|
||||
#include <memory>
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
// functions for nice debug output
|
||||
static QString getKeyDerivationError(TOX_ERR_KEY_DERIVATION error);
|
||||
@ -51,9 +51,10 @@ ToxEncrypt::~ToxEncrypt()
|
||||
* @brief Constructs a ToxEncrypt object from a Tox_Pass_Key.
|
||||
* @param key Derived key to use for encryption and decryption.
|
||||
*/
|
||||
ToxEncrypt::ToxEncrypt(Tox_Pass_Key* key) :
|
||||
passKey{key}
|
||||
{}
|
||||
ToxEncrypt::ToxEncrypt(Tox_Pass_Key* key)
|
||||
: passKey{key}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the minimum number of bytes needed for isEncrypted()
|
||||
@ -72,8 +73,7 @@ int ToxEncrypt::getMinBytes()
|
||||
*/
|
||||
bool ToxEncrypt::isEncrypted(const QByteArray& ciphertext)
|
||||
{
|
||||
if (ciphertext.length() < TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
|
||||
{
|
||||
if (ciphertext.length() < TOX_PASS_ENCRYPTION_EXTRA_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -89,8 +89,7 @@ bool ToxEncrypt::isEncrypted(const QByteArray& ciphertext)
|
||||
*/
|
||||
QByteArray ToxEncrypt::encryptPass(const QString& password, const QByteArray& plaintext)
|
||||
{
|
||||
if (password.length() == 0)
|
||||
{
|
||||
if (password.length() == 0) {
|
||||
qWarning() << "Empty password supplied, probably not what you intended.";
|
||||
}
|
||||
|
||||
@ -103,8 +102,7 @@ QByteArray ToxEncrypt::encryptPass(const QString& password, const QByteArray& pl
|
||||
static_cast<size_t>(pass.size()),
|
||||
reinterpret_cast<uint8_t*>(ciphertext.data()), &error);
|
||||
|
||||
if (error != TOX_ERR_ENCRYPTION_OK)
|
||||
{
|
||||
if (error != TOX_ERR_ENCRYPTION_OK) {
|
||||
qCritical() << getEncryptionError(error);
|
||||
return QByteArray{};
|
||||
}
|
||||
@ -121,14 +119,12 @@ QByteArray ToxEncrypt::encryptPass(const QString& password, const QByteArray& pl
|
||||
*/
|
||||
QByteArray ToxEncrypt::decryptPass(const QString& password, const QByteArray& ciphertext)
|
||||
{
|
||||
if (!isEncrypted(ciphertext))
|
||||
{
|
||||
if (!isEncrypted(ciphertext)) {
|
||||
qWarning() << "The data was not encrypted using this module or it's corrupted.";
|
||||
return QByteArray{};
|
||||
}
|
||||
|
||||
if (password.length() == 0)
|
||||
{
|
||||
if (password.length() == 0) {
|
||||
qDebug() << "Empty password supplied, probably not what you intended.";
|
||||
}
|
||||
|
||||
@ -138,11 +134,10 @@ QByteArray ToxEncrypt::decryptPass(const QString& password, const QByteArray& ci
|
||||
tox_pass_decrypt(reinterpret_cast<const uint8_t*>(ciphertext.constData()),
|
||||
static_cast<size_t>(ciphertext.size()),
|
||||
reinterpret_cast<const uint8_t*>(pass.constData()),
|
||||
static_cast<size_t>(pass.size()),
|
||||
reinterpret_cast<uint8_t*>(plaintext.data()), &error);
|
||||
static_cast<size_t>(pass.size()), reinterpret_cast<uint8_t*>(plaintext.data()),
|
||||
&error);
|
||||
|
||||
if (error != TOX_ERR_DECRYPTION_OK)
|
||||
{
|
||||
if (error != TOX_ERR_DECRYPTION_OK) {
|
||||
qWarning() << getDecryptionError(error);
|
||||
return QByteArray{};
|
||||
}
|
||||
@ -166,8 +161,7 @@ std::unique_ptr<ToxEncrypt> ToxEncrypt::makeToxEncrypt(const QString& password)
|
||||
tox_pass_key_derive(passKey, reinterpret_cast<const uint8_t*>(pass.constData()),
|
||||
static_cast<size_t>(pass.length()), &error);
|
||||
|
||||
if (error != TOX_ERR_KEY_DERIVATION_OK)
|
||||
{
|
||||
if (error != TOX_ERR_KEY_DERIVATION_OK) {
|
||||
tox_pass_key_free(passKey);
|
||||
qCritical() << getKeyDerivationError(error);
|
||||
return std::unique_ptr<ToxEncrypt>{};
|
||||
@ -187,8 +181,7 @@ std::unique_ptr<ToxEncrypt> ToxEncrypt::makeToxEncrypt(const QString& password)
|
||||
*/
|
||||
std::unique_ptr<ToxEncrypt> ToxEncrypt::makeToxEncrypt(const QString& password, const QByteArray& toxSave)
|
||||
{
|
||||
if (!isEncrypted(toxSave))
|
||||
{
|
||||
if (!isEncrypted(toxSave)) {
|
||||
qWarning() << "The data was not encrypted using this module or it's corrupted.";
|
||||
return std::unique_ptr<ToxEncrypt>{};
|
||||
}
|
||||
@ -197,8 +190,7 @@ std::unique_ptr<ToxEncrypt> ToxEncrypt::makeToxEncrypt(const QString& password,
|
||||
uint8_t salt[TOX_PASS_SALT_LENGTH];
|
||||
tox_get_salt(reinterpret_cast<const uint8_t*>(toxSave.constData()), salt, &saltError);
|
||||
|
||||
if (saltError != TOX_ERR_GET_SALT_OK)
|
||||
{
|
||||
if (saltError != TOX_ERR_GET_SALT_OK) {
|
||||
qWarning() << getSaltError(saltError);
|
||||
return std::unique_ptr<ToxEncrypt>{};
|
||||
}
|
||||
@ -207,10 +199,9 @@ std::unique_ptr<ToxEncrypt> ToxEncrypt::makeToxEncrypt(const QString& password,
|
||||
QByteArray pass = password.toUtf8();
|
||||
TOX_ERR_KEY_DERIVATION keyError;
|
||||
tox_pass_key_derive_with_salt(passKey, reinterpret_cast<const uint8_t*>(pass.constData()),
|
||||
static_cast<size_t>(pass.length()), salt, &keyError);
|
||||
static_cast<size_t>(pass.length()), salt, &keyError);
|
||||
|
||||
if (keyError != TOX_ERR_KEY_DERIVATION_OK)
|
||||
{
|
||||
if (keyError != TOX_ERR_KEY_DERIVATION_OK) {
|
||||
tox_pass_key_free(passKey);
|
||||
qWarning() << getKeyDerivationError(keyError);
|
||||
return std::unique_ptr<ToxEncrypt>{};
|
||||
@ -226,21 +217,18 @@ std::unique_ptr<ToxEncrypt> ToxEncrypt::makeToxEncrypt(const QString& password,
|
||||
*/
|
||||
QByteArray ToxEncrypt::encrypt(const QByteArray& plaintext) const
|
||||
{
|
||||
if (!passKey)
|
||||
{
|
||||
if (!passKey) {
|
||||
qCritical() << "The passKey is invalid.";
|
||||
return QByteArray{};
|
||||
}
|
||||
|
||||
QByteArray ciphertext(plaintext.length() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0x00);
|
||||
TOX_ERR_ENCRYPTION error;
|
||||
tox_pass_key_encrypt(passKey,
|
||||
reinterpret_cast<const uint8_t*>(plaintext.constData()),
|
||||
tox_pass_key_encrypt(passKey, reinterpret_cast<const uint8_t*>(plaintext.constData()),
|
||||
static_cast<size_t>(plaintext.size()),
|
||||
reinterpret_cast<uint8_t*>(ciphertext.data()), &error);
|
||||
|
||||
if (error != TOX_ERR_ENCRYPTION_OK)
|
||||
{
|
||||
if (error != TOX_ERR_ENCRYPTION_OK) {
|
||||
qCritical() << getEncryptionError(error);
|
||||
return QByteArray{};
|
||||
}
|
||||
@ -256,21 +244,18 @@ QByteArray ToxEncrypt::encrypt(const QByteArray& plaintext) const
|
||||
*/
|
||||
QByteArray ToxEncrypt::decrypt(const QByteArray& ciphertext) const
|
||||
{
|
||||
if (!isEncrypted(ciphertext))
|
||||
{
|
||||
if (!isEncrypted(ciphertext)) {
|
||||
qWarning() << "The data was not encrypted using this module or it's corrupted.";
|
||||
return QByteArray{};
|
||||
}
|
||||
|
||||
QByteArray plaintext(ciphertext.length() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0x00);
|
||||
TOX_ERR_DECRYPTION error;
|
||||
tox_pass_key_decrypt(passKey,
|
||||
reinterpret_cast<const uint8_t*>(ciphertext.constData()),
|
||||
tox_pass_key_decrypt(passKey, reinterpret_cast<const uint8_t*>(ciphertext.constData()),
|
||||
static_cast<size_t>(ciphertext.size()),
|
||||
reinterpret_cast<uint8_t*>(plaintext.data()), &error);
|
||||
|
||||
if (error != TOX_ERR_DECRYPTION_OK)
|
||||
{
|
||||
if (error != TOX_ERR_DECRYPTION_OK) {
|
||||
qWarning() << getDecryptionError(error);
|
||||
return QByteArray{};
|
||||
}
|
||||
@ -285,14 +270,15 @@ QByteArray ToxEncrypt::decrypt(const QByteArray& ciphertext) const
|
||||
*/
|
||||
QString getKeyDerivationError(TOX_ERR_KEY_DERIVATION error)
|
||||
{
|
||||
switch(error)
|
||||
{
|
||||
switch (error) {
|
||||
case TOX_ERR_KEY_DERIVATION_OK:
|
||||
return QStringLiteral("The function returned successfully.");
|
||||
case TOX_ERR_KEY_DERIVATION_NULL:
|
||||
return QStringLiteral("One of the arguments to the function was NULL when it was not expected.");
|
||||
return QStringLiteral(
|
||||
"One of the arguments to the function was NULL when it was not expected.");
|
||||
case TOX_ERR_KEY_DERIVATION_FAILED:
|
||||
return QStringLiteral("The crypto lib was unable to derive a key from the given passphrase.");
|
||||
return QStringLiteral(
|
||||
"The crypto lib was unable to derive a key from the given passphrase.");
|
||||
default:
|
||||
return QStringLiteral("Unknown key derivation error.");
|
||||
}
|
||||
@ -305,14 +291,15 @@ QString getKeyDerivationError(TOX_ERR_KEY_DERIVATION error)
|
||||
*/
|
||||
QString getEncryptionError(TOX_ERR_ENCRYPTION error)
|
||||
{
|
||||
switch(error)
|
||||
{
|
||||
switch (error) {
|
||||
case TOX_ERR_ENCRYPTION_OK:
|
||||
return QStringLiteral("The function returned successfully.");
|
||||
case TOX_ERR_ENCRYPTION_NULL:
|
||||
return QStringLiteral("One of the arguments to the function was NULL when it was not expected.");
|
||||
return QStringLiteral(
|
||||
"One of the arguments to the function was NULL when it was not expected.");
|
||||
case TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED:
|
||||
return QStringLiteral("The crypto lib was unable to derive a key from the given passphrase.");
|
||||
return QStringLiteral(
|
||||
"The crypto lib was unable to derive a key from the given passphrase.");
|
||||
case TOX_ERR_ENCRYPTION_FAILED:
|
||||
return QStringLiteral("The encryption itself failed.");
|
||||
default:
|
||||
@ -327,14 +314,15 @@ QString getEncryptionError(TOX_ERR_ENCRYPTION error)
|
||||
*/
|
||||
QString getDecryptionError(TOX_ERR_DECRYPTION error)
|
||||
{
|
||||
switch(error)
|
||||
{
|
||||
switch (error) {
|
||||
case TOX_ERR_DECRYPTION_OK:
|
||||
return QStringLiteral("The function returned successfully.");
|
||||
case TOX_ERR_DECRYPTION_NULL:
|
||||
return QStringLiteral("One of the arguments to the function was NULL when it was not expected.");
|
||||
return QStringLiteral(
|
||||
"One of the arguments to the function was NULL when it was not expected.");
|
||||
case TOX_ERR_DECRYPTION_INVALID_LENGTH:
|
||||
return QStringLiteral("The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes.");
|
||||
return QStringLiteral(
|
||||
"The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes.");
|
||||
case TOX_ERR_DECRYPTION_BAD_FORMAT:
|
||||
return QStringLiteral("The input data is missing the magic number or is corrupted.");
|
||||
default:
|
||||
@ -349,12 +337,12 @@ QString getDecryptionError(TOX_ERR_DECRYPTION error)
|
||||
*/
|
||||
QString getSaltError(TOX_ERR_GET_SALT error)
|
||||
{
|
||||
switch(error)
|
||||
{
|
||||
switch (error) {
|
||||
case TOX_ERR_GET_SALT_OK:
|
||||
return QStringLiteral("The function returned successfully.");
|
||||
case TOX_ERR_GET_SALT_NULL:
|
||||
return QStringLiteral("One of the arguments to the function was NULL when it was not expected.");
|
||||
return QStringLiteral(
|
||||
"One of the arguments to the function was NULL when it was not expected.");
|
||||
case TOX_ERR_GET_SALT_BAD_FORMAT:
|
||||
return QStringLiteral("The input data is missing the magic number or is corrupted.");
|
||||
default:
|
||||
|
@ -17,8 +17,8 @@
|
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#ifndef TOXENCRYPT_H
|
||||
#define TOXENCRYPT_H
|
||||
@ -40,7 +40,8 @@ public:
|
||||
static QByteArray encryptPass(const QString& password, const QByteArray& plaintext);
|
||||
static QByteArray decryptPass(const QString& password, const QByteArray& ciphertext);
|
||||
static std::unique_ptr<ToxEncrypt> makeToxEncrypt(const QString& password);
|
||||
static std::unique_ptr<ToxEncrypt> makeToxEncrypt(const QString& password, const QByteArray& toxSave);
|
||||
static std::unique_ptr<ToxEncrypt> makeToxEncrypt(const QString& password,
|
||||
const QByteArray& toxSave);
|
||||
QByteArray encrypt(const QByteArray& plaintext) const;
|
||||
QByteArray decrypt(const QByteArray& ciphertext) const;
|
||||
|
||||
|
@ -24,17 +24,17 @@
|
||||
|
||||
#include <tox/tox.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <QRegularExpression>
|
||||
#include <cstdint>
|
||||
|
||||
// Tox doesn't publicly define these
|
||||
#define NOSPAM_BYTES 4
|
||||
#define CHECKSUM_BYTES 2
|
||||
#define NOSPAM_BYTES 4
|
||||
#define CHECKSUM_BYTES 2
|
||||
|
||||
#define PUBLIC_KEY_HEX_CHARS (2*TOX_PUBLIC_KEY_SIZE)
|
||||
#define NOSPAM_HEX_CHARS (2*NOSPAM_BYTES)
|
||||
#define CHECKSUM_HEX_CHARS (2*CHECKSUM_BYTES)
|
||||
#define TOXID_HEX_CHARS (2*TOX_ADDRESS_SIZE)
|
||||
#define PUBLIC_KEY_HEX_CHARS (2 * TOX_PUBLIC_KEY_SIZE)
|
||||
#define NOSPAM_HEX_CHARS (2 * NOSPAM_BYTES)
|
||||
#define CHECKSUM_HEX_CHARS (2 * CHECKSUM_BYTES)
|
||||
#define TOXID_HEX_CHARS (2 * TOX_ADDRESS_SIZE)
|
||||
|
||||
const QRegularExpression ToxId::ToxIdRegEx(QString("(^|\\s)[A-Fa-f0-9]{%1}($|\\s)").arg(TOXID_HEX_CHARS));
|
||||
|
||||
@ -59,16 +59,18 @@ const QRegularExpression ToxId::ToxIdRegEx(QString("(^|\\s)[A-Fa-f0-9]{%1}($|\\s
|
||||
* @brief The default constructor. Creates an empty Tox ID.
|
||||
*/
|
||||
ToxId::ToxId()
|
||||
: toxId()
|
||||
{}
|
||||
: toxId()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The copy constructor.
|
||||
* @param other ToxId to copy
|
||||
*/
|
||||
ToxId::ToxId(const ToxId& other)
|
||||
: toxId(other.toxId)
|
||||
{}
|
||||
: toxId(other.toxId)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a Tox ID from a QString.
|
||||
@ -82,17 +84,12 @@ ToxId::ToxId(const ToxId& other)
|
||||
ToxId::ToxId(const QString& id)
|
||||
{
|
||||
// TODO: remove construction from PK only
|
||||
if (isToxId(id))
|
||||
{
|
||||
if (isToxId(id)) {
|
||||
toxId = QByteArray::fromHex(id.toLatin1());
|
||||
}
|
||||
else if(id.length() >= PUBLIC_KEY_HEX_CHARS)
|
||||
{
|
||||
} else if (id.length() >= PUBLIC_KEY_HEX_CHARS) {
|
||||
toxId = QByteArray::fromHex(id.left(PUBLIC_KEY_HEX_CHARS).toLatin1());
|
||||
}
|
||||
else
|
||||
{
|
||||
toxId = QByteArray(); // invalid id string
|
||||
} else {
|
||||
toxId = QByteArray(); // invalid id string
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +120,7 @@ ToxId::ToxId(const QByteArray& rawId)
|
||||
*/
|
||||
ToxId::ToxId(const uint8_t* rawId, int len)
|
||||
{
|
||||
QByteArray tmpId(reinterpret_cast<const char *>(rawId), len);
|
||||
QByteArray tmpId(reinterpret_cast<const char*>(rawId), len);
|
||||
constructToxId(tmpId);
|
||||
}
|
||||
|
||||
@ -131,18 +128,12 @@ ToxId::ToxId(const uint8_t* rawId, int len)
|
||||
void ToxId::constructToxId(const QByteArray& rawId)
|
||||
{
|
||||
// TODO: remove construction from PK only
|
||||
if(rawId.length() == TOX_SECRET_KEY_SIZE)
|
||||
{
|
||||
toxId = QByteArray(rawId); // construct from PK only
|
||||
}
|
||||
else if (rawId.length() == TOX_ADDRESS_SIZE
|
||||
&& isToxId(rawId.toHex().toUpper()))
|
||||
{
|
||||
toxId = QByteArray(rawId); // construct from full toxid
|
||||
}
|
||||
else
|
||||
{
|
||||
toxId = QByteArray(); // invalid id
|
||||
if (rawId.length() == TOX_SECRET_KEY_SIZE) {
|
||||
toxId = QByteArray(rawId); // construct from PK only
|
||||
} else if (rawId.length() == TOX_ADDRESS_SIZE && isToxId(rawId.toHex().toUpper())) {
|
||||
toxId = QByteArray(rawId); // construct from full toxid
|
||||
} else {
|
||||
toxId = QByteArray(); // invalid id
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,8 +181,7 @@ void ToxId::clear()
|
||||
*/
|
||||
const uint8_t* ToxId::getBytes() const
|
||||
{
|
||||
if(isValid())
|
||||
{
|
||||
if (isValid()) {
|
||||
return reinterpret_cast<const uint8_t*>(toxId.constData());
|
||||
}
|
||||
|
||||
@ -213,8 +203,7 @@ ToxPk ToxId::getPublicKey() const
|
||||
*/
|
||||
QString ToxId::getNoSpamString() const
|
||||
{
|
||||
if(toxId.length() == TOX_ADDRESS_SIZE)
|
||||
{
|
||||
if (toxId.length() == TOX_ADDRESS_SIZE) {
|
||||
return toxId.mid(TOX_PUBLIC_KEY_SIZE, NOSPAM_BYTES).toHex().toUpper();
|
||||
}
|
||||
|
||||
@ -249,8 +238,7 @@ bool ToxId::isToxId(const QString& id)
|
||||
*/
|
||||
bool ToxId::isValid() const
|
||||
{
|
||||
if(toxId.length() != TOX_ADDRESS_SIZE)
|
||||
{
|
||||
if (toxId.length() != TOX_ADDRESS_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -260,8 +248,7 @@ bool ToxId::isValid() const
|
||||
QByteArray checksum = toxId.right(CHECKSUM_BYTES);
|
||||
QByteArray calculated(CHECKSUM_BYTES, 0x00);
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
for (int i = 0; i < size; i++) {
|
||||
calculated[i % 2] = calculated[i % 2] ^ data[i];
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,9 @@
|
||||
|
||||
#include "toxpk.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <cstdint>
|
||||
|
||||
class ToxId
|
||||
{
|
||||
@ -43,7 +43,7 @@ public:
|
||||
bool isValid() const;
|
||||
|
||||
static bool isValidToxId(const QString& id);
|
||||
static bool isToxId(const QString &id);
|
||||
static bool isToxId(const QString& id);
|
||||
const uint8_t* getBytes() const;
|
||||
QByteArray getToxId() const;
|
||||
ToxPk getPublicKey() const;
|
||||
|
@ -14,16 +14,18 @@
|
||||
* @brief The default constructor. Creates an empty Tox key.
|
||||
*/
|
||||
ToxPk::ToxPk()
|
||||
: key()
|
||||
{}
|
||||
: key()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The copy constructor.
|
||||
* @param other ToxPk to copy
|
||||
*/
|
||||
ToxPk::ToxPk(const ToxPk& other)
|
||||
: key(other.key)
|
||||
{}
|
||||
: key(other.key)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs a ToxPk from bytes.
|
||||
@ -32,12 +34,9 @@ ToxPk::ToxPk(const ToxPk& other)
|
||||
*/
|
||||
ToxPk::ToxPk(const QByteArray& rawId)
|
||||
{
|
||||
if(rawId.length() == TOX_PUBLIC_KEY_SIZE)
|
||||
{
|
||||
if (rawId.length() == TOX_PUBLIC_KEY_SIZE) {
|
||||
key = QByteArray(rawId);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
key = QByteArray();
|
||||
}
|
||||
}
|
||||
@ -88,8 +87,7 @@ QString ToxPk::toString() const
|
||||
*/
|
||||
const uint8_t* ToxPk::getBytes() const
|
||||
{
|
||||
if(key.isEmpty())
|
||||
{
|
||||
if (key.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
#ifndef TOXPK_H
|
||||
#define TOXPK_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <cstdint>
|
||||
|
||||
class ToxPk
|
||||
{
|
||||
@ -21,6 +21,7 @@ public:
|
||||
bool isEmpty() const;
|
||||
|
||||
static int getPkSize();
|
||||
|
||||
private:
|
||||
QByteArray key;
|
||||
};
|
||||
|
@ -23,8 +23,8 @@
|
||||
#include "src/group.h"
|
||||
#include "src/grouplist.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/widget/form/chatform.h"
|
||||
|
||||
Friend::Friend(uint32_t friendId, const ToxPk& friendPk)
|
||||
@ -35,8 +35,7 @@ Friend::Friend(uint32_t friendId, const ToxPk& friendPk)
|
||||
, hasNewEvents(false)
|
||||
, friendStatus(Status::Offline)
|
||||
{
|
||||
if (userName.isEmpty())
|
||||
{
|
||||
if (userName.isEmpty()) {
|
||||
userName = friendPk.toString();
|
||||
}
|
||||
|
||||
@ -55,8 +54,7 @@ Friend::~Friend()
|
||||
*/
|
||||
void Friend::loadHistory()
|
||||
{
|
||||
if (Nexus::getProfile()->isHistoryEnabled())
|
||||
{
|
||||
if (Nexus::getProfile()->isHistoryEnabled()) {
|
||||
chatForm->loadHistory(QDateTime::currentDateTime().addDays(-7), true);
|
||||
}
|
||||
|
||||
@ -65,13 +63,11 @@ void Friend::loadHistory()
|
||||
|
||||
void Friend::setName(QString name)
|
||||
{
|
||||
if (name.isEmpty())
|
||||
{
|
||||
if (name.isEmpty()) {
|
||||
name = friendPk.toString();
|
||||
}
|
||||
|
||||
if (userName != name)
|
||||
{
|
||||
if (userName != name) {
|
||||
userName = name;
|
||||
emit nameChanged(friendId, name);
|
||||
}
|
||||
@ -79,8 +75,7 @@ void Friend::setName(QString name)
|
||||
|
||||
void Friend::setAlias(QString alias)
|
||||
{
|
||||
if (userAlias != alias)
|
||||
{
|
||||
if (userAlias != alias) {
|
||||
userAlias = alias;
|
||||
emit aliasChanged(friendId, alias);
|
||||
}
|
||||
@ -88,8 +83,7 @@ void Friend::setAlias(QString alias)
|
||||
|
||||
void Friend::setStatusMessage(QString message)
|
||||
{
|
||||
if (statusMessage != message)
|
||||
{
|
||||
if (statusMessage != message) {
|
||||
statusMessage = message;
|
||||
emit statusMessageChanged(friendId, message);
|
||||
}
|
||||
@ -102,8 +96,7 @@ QString Friend::getStatusMessage()
|
||||
|
||||
QString Friend::getDisplayedName() const
|
||||
{
|
||||
if (userAlias.isEmpty())
|
||||
{
|
||||
if (userAlias.isEmpty()) {
|
||||
return userName;
|
||||
}
|
||||
|
||||
@ -137,8 +130,7 @@ bool Friend::getEventFlag() const
|
||||
|
||||
void Friend::setStatus(Status s)
|
||||
{
|
||||
if (friendStatus != s)
|
||||
{
|
||||
if (friendStatus != s) {
|
||||
friendStatus = s;
|
||||
emit statusChanged(friendId, friendStatus);
|
||||
}
|
||||
@ -149,7 +141,7 @@ Status Friend::getStatus() const
|
||||
return friendStatus;
|
||||
}
|
||||
|
||||
ChatForm *Friend::getChatForm()
|
||||
ChatForm* Friend::getChatForm()
|
||||
{
|
||||
return chatForm;
|
||||
}
|
||||
|
@ -20,10 +20,10 @@
|
||||
#ifndef FRIEND_H
|
||||
#define FRIEND_H
|
||||
|
||||
#include "core/toxid.h"
|
||||
#include "src/core/corestructs.h"
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include "src/core/corestructs.h"
|
||||
#include "core/toxid.h"
|
||||
|
||||
class FriendWidget;
|
||||
class ChatForm;
|
||||
@ -56,7 +56,7 @@ public:
|
||||
void setStatus(Status s);
|
||||
Status getStatus() const;
|
||||
|
||||
ChatForm *getChatForm();
|
||||
ChatForm* getChatForm();
|
||||
|
||||
signals:
|
||||
// TODO: move signals to DB object
|
||||
|
@ -20,9 +20,9 @@
|
||||
#include "friend.h"
|
||||
#include "friendlist.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include <QMenu>
|
||||
#include <QDebug>
|
||||
#include <QHash>
|
||||
#include <QMenu>
|
||||
|
||||
QHash<int, Friend*> FriendList::friendList;
|
||||
QHash<QByteArray, int> FriendList::key2id;
|
||||
@ -52,8 +52,7 @@ Friend* FriendList::findFriend(int friendId)
|
||||
void FriendList::removeFriend(int friendId, bool fake)
|
||||
{
|
||||
auto f_it = friendList.find(friendId);
|
||||
if (f_it != friendList.end())
|
||||
{
|
||||
if (f_it != friendList.end()) {
|
||||
if (!fake)
|
||||
Settings::getInstance().removeFriendSettings(f_it.value()->getPublicKey());
|
||||
friendList.erase(f_it);
|
||||
@ -70,8 +69,7 @@ void FriendList::clear()
|
||||
Friend* FriendList::findFriend(const ToxPk& friendPk)
|
||||
{
|
||||
auto id = key2id.find(friendPk.getKey());
|
||||
if (id != key2id.end())
|
||||
{
|
||||
if (id != key2id.end()) {
|
||||
Friend* f = findFriend(*id);
|
||||
if (!f)
|
||||
return nullptr;
|
||||
|
@ -20,8 +20,10 @@
|
||||
#ifndef FRIENDLIST_H
|
||||
#define FRIENDLIST_H
|
||||
|
||||
template <class T> class QList;
|
||||
template <class A, class B> class QHash;
|
||||
template <class T>
|
||||
class QList;
|
||||
template <class A, class B>
|
||||
class QHash;
|
||||
class Friend;
|
||||
class QByteArray;
|
||||
class ToxPk;
|
||||
|
@ -18,22 +18,24 @@
|
||||
*/
|
||||
|
||||
#include "group.h"
|
||||
#include "widget/groupwidget.h"
|
||||
#include "widget/form/groupchatform.h"
|
||||
#include "friendlist.h"
|
||||
#include "friend.h"
|
||||
#include "friendlist.h"
|
||||
#include "src/core/core.h"
|
||||
#include "widget/form/groupchatform.h"
|
||||
#include "widget/groupwidget.h"
|
||||
#include "widget/gui.h"
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
|
||||
Group::Group(int GroupId, QString Name, bool IsAvGroupchat)
|
||||
: groupId(GroupId), nPeers{0}, avGroupchat{IsAvGroupchat}
|
||||
: groupId(GroupId)
|
||||
, nPeers{0}
|
||||
, avGroupchat{IsAvGroupchat}
|
||||
{
|
||||
widget = new GroupWidget(groupId, Name);
|
||||
chatForm = new GroupChatForm(this);
|
||||
|
||||
//in groupchats, we only notify on messages containing your name <-- dumb
|
||||
// in groupchats, we only notify on messages containing your name <-- dumb
|
||||
// sound notifications should be on all messages, but system popup notification
|
||||
// on naming is appropriate
|
||||
hasNewMessages = 0;
|
||||
@ -54,13 +56,10 @@ void Group::updatePeer(int peerId, QString name)
|
||||
toxids[peerPk] = name;
|
||||
|
||||
Friend* f = FriendList::findFriend(peerKey);
|
||||
if (f != nullptr && f->hasAlias())
|
||||
{
|
||||
if (f != nullptr && f->hasAlias()) {
|
||||
peers[peerId] = f->getDisplayedName();
|
||||
toxids[peerPk] = f->getDisplayedName();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
widget->onUserListChanged();
|
||||
chatForm->onUserListChanged();
|
||||
emit userListChanged(getGroupWidget());
|
||||
@ -88,8 +87,7 @@ void Group::regeneratePeerList()
|
||||
peers = core->getGroupPeerNames(groupId);
|
||||
toxids.clear();
|
||||
nPeers = peers.size();
|
||||
for (int i = 0; i < nPeers; ++i)
|
||||
{
|
||||
for (int i = 0; i < nPeers; ++i) {
|
||||
ToxPk id = core->getGroupPeerPk(groupId, i);
|
||||
ToxPk self = core->getSelfId().getPublicKey();
|
||||
if (id == self)
|
||||
@ -98,11 +96,11 @@ void Group::regeneratePeerList()
|
||||
QByteArray peerPk = id.getKey();
|
||||
toxids[peerPk] = peers[i];
|
||||
if (toxids[peerPk].isEmpty())
|
||||
toxids[peerPk] = tr("<Empty>", "Placeholder when someone's name in a group chat is empty");
|
||||
toxids[peerPk] =
|
||||
tr("<Empty>", "Placeholder when someone's name in a group chat is empty");
|
||||
|
||||
Friend* f = FriendList::findFriend(id);
|
||||
if (f != nullptr && f->hasAlias())
|
||||
{
|
||||
if (f != nullptr && f->hasAlias()) {
|
||||
peers[i] = f->getDisplayedName();
|
||||
toxids[peerPk] = f->getDisplayedName();
|
||||
}
|
||||
@ -128,12 +126,12 @@ int Group::getPeersCount() const
|
||||
return nPeers;
|
||||
}
|
||||
|
||||
GroupChatForm *Group::getChatForm()
|
||||
GroupChatForm* Group::getChatForm()
|
||||
{
|
||||
return chatForm;
|
||||
}
|
||||
|
||||
GroupWidget *Group::getGroupWidget()
|
||||
GroupWidget* Group::getGroupWidget()
|
||||
{
|
||||
return widget;
|
||||
}
|
||||
@ -168,7 +166,7 @@ int Group::getMentionedFlag() const
|
||||
return userWasMentioned;
|
||||
}
|
||||
|
||||
QString Group::resolveToxId(const ToxPk &id) const
|
||||
QString Group::resolveToxId(const ToxPk& id) const
|
||||
{
|
||||
QByteArray key = id.getKey();
|
||||
auto it = toxids.find(key);
|
||||
|
@ -45,8 +45,8 @@ public:
|
||||
QStringList getPeerList() const;
|
||||
bool isSelfPeerNumber(int peernumber) const;
|
||||
|
||||
GroupChatForm *getChatForm();
|
||||
GroupWidget *getGroupWidget();
|
||||
GroupChatForm* getChatForm();
|
||||
GroupWidget* getGroupWidget();
|
||||
|
||||
void setEventFlag(int f);
|
||||
int getEventFlag() const;
|
||||
@ -74,7 +74,6 @@ private:
|
||||
int nPeers;
|
||||
int selfPeerNum = -1;
|
||||
bool avGroupchat;
|
||||
|
||||
};
|
||||
|
||||
#endif // GROUP_H
|
||||
|
@ -26,19 +26,17 @@
|
||||
*/
|
||||
|
||||
GroupInvite::GroupInvite(int32_t friendID, uint8_t inviteType, const QByteArray& data)
|
||||
: friendId { friendID }
|
||||
, type { inviteType }
|
||||
, invite { data }
|
||||
, date { QDateTime::currentDateTime() }
|
||||
: friendId{friendID}
|
||||
, type{inviteType}
|
||||
, invite{data}
|
||||
, date{QDateTime::currentDateTime()}
|
||||
{
|
||||
}
|
||||
|
||||
bool GroupInvite::operator ==(const GroupInvite& other) const
|
||||
bool GroupInvite::operator==(const GroupInvite& other) const
|
||||
{
|
||||
return friendId == other.friendId &&
|
||||
type == other.type &&
|
||||
invite == other.invite &&
|
||||
date == other.date;
|
||||
return friendId == other.friendId && type == other.type && invite == other.invite
|
||||
&& date == other.date;
|
||||
}
|
||||
|
||||
int32_t GroupInvite::getFriendId() const
|
||||
|
@ -20,15 +20,15 @@
|
||||
#ifndef GROUPINVITE_H
|
||||
#define GROUPINVITE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <cstdint>
|
||||
|
||||
class GroupInvite
|
||||
{
|
||||
public:
|
||||
GroupInvite(int32_t friendID, uint8_t inviteType, const QByteArray& data);
|
||||
bool operator ==(const GroupInvite& other) const;
|
||||
bool operator==(const GroupInvite& other) const;
|
||||
|
||||
int32_t getFriendId() const;
|
||||
uint8_t getType() const;
|
||||
|
@ -19,8 +19,8 @@
|
||||
|
||||
#include "grouplist.h"
|
||||
#include "group.h"
|
||||
#include <QHash>
|
||||
#include <QDebug>
|
||||
#include <QHash>
|
||||
|
||||
QHash<int, Group*> GroupList::groupList;
|
||||
|
||||
@ -48,13 +48,12 @@ Group* GroupList::findGroup(int groupId)
|
||||
void GroupList::removeGroup(int groupId, bool /*fake*/)
|
||||
{
|
||||
auto g_it = groupList.find(groupId);
|
||||
if (g_it != groupList.end())
|
||||
{
|
||||
if (g_it != groupList.end()) {
|
||||
groupList.erase(g_it);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Group *> GroupList::getAllGroups()
|
||||
QList<Group*> GroupList::getAllGroups()
|
||||
{
|
||||
QList<Group*> res;
|
||||
|
||||
|
@ -20,8 +20,10 @@
|
||||
#ifndef GROUPLIST_H
|
||||
#define GROUPLIST_H
|
||||
|
||||
template <class A, class B> class QHash;
|
||||
template <class T> class QList;
|
||||
template <class A, class B>
|
||||
class QHash;
|
||||
template <class T>
|
||||
class QList;
|
||||
class Group;
|
||||
class QString;
|
||||
|
||||
|
124
src/ipc.cpp
124
src/ipc.cpp
@ -51,33 +51,26 @@ IPC::IPC()
|
||||
// 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
|
||||
// If the owner exits normally, it can set the timestamp to 0 first to immediately give
|
||||
// ownership
|
||||
|
||||
std::default_random_engine randEngine((std::random_device())());
|
||||
std::uniform_int_distribution<uint64_t> distribution;
|
||||
globalId = distribution(randEngine);
|
||||
qDebug() << "Our global IPC ID is " << globalId;
|
||||
if (globalMemory.create(sizeof(IPCMemory)))
|
||||
{
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
if (globalMemory.create(sizeof(IPCMemory))) {
|
||||
if (globalMemory.lock()) {
|
||||
IPCMemory* mem = global();
|
||||
memset(mem, 0, sizeof(IPCMemory));
|
||||
mem->globalId = globalId;
|
||||
mem->lastProcessed = time(0);
|
||||
globalMemory.unlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "Couldn't lock to take ownership";
|
||||
}
|
||||
}
|
||||
else if (globalMemory.attach())
|
||||
{
|
||||
} else if (globalMemory.attach()) {
|
||||
qDebug() << "Attaching to the global shared memory";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qDebug() << "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
|
||||
}
|
||||
@ -87,10 +80,8 @@ IPC::IPC()
|
||||
|
||||
IPC::~IPC()
|
||||
{
|
||||
if (isCurrentOwner())
|
||||
{
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
if (isCurrentOwner()) {
|
||||
if (globalMemory.lock()) {
|
||||
global()->globalId = 0;
|
||||
globalMemory.unlock();
|
||||
}
|
||||
@ -113,7 +104,7 @@ IPC& IPC::getInstance()
|
||||
* @param dest Settings::getCurrentProfileId() or 0 (main instance, default).
|
||||
* @return Time the event finished.
|
||||
*/
|
||||
time_t IPC::postEvent(const QString &name, const QByteArray& data, uint32_t dest)
|
||||
time_t IPC::postEvent(const QString& name, const QByteArray& data, uint32_t dest)
|
||||
{
|
||||
QByteArray binName = name.toUtf8();
|
||||
if (binName.length() > (int32_t)sizeof(IPCEvent::name))
|
||||
@ -122,20 +113,17 @@ time_t IPC::postEvent(const QString &name, const QByteArray& data, uint32_t dest
|
||||
if (data.length() > (int32_t)sizeof(IPCEvent::data))
|
||||
return 0;
|
||||
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
if (globalMemory.lock()) {
|
||||
IPCEvent* evt = nullptr;
|
||||
IPCMemory* mem = global();
|
||||
time_t result = 0;
|
||||
|
||||
for (uint32_t i = 0; !evt && i < EVENT_QUEUE_SIZE; ++i)
|
||||
{
|
||||
for (uint32_t i = 0; !evt && i < EVENT_QUEUE_SIZE; ++i) {
|
||||
if (mem->events[i].posted == 0)
|
||||
evt = &mem->events[i];
|
||||
}
|
||||
|
||||
if (evt)
|
||||
{
|
||||
if (evt) {
|
||||
memset(evt, 0, sizeof(IPCEvent));
|
||||
memcpy(evt->name, binName.constData(), binName.length());
|
||||
memcpy(evt->data, data.constData(), data.length());
|
||||
@ -146,8 +134,7 @@ time_t IPC::postEvent(const QString &name, const QByteArray& data, uint32_t dest
|
||||
}
|
||||
globalMemory.unlock();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
} else
|
||||
qDebug() << "Failed to lock in postEvent()";
|
||||
|
||||
return 0;
|
||||
@ -155,11 +142,9 @@ time_t IPC::postEvent(const QString &name, const QByteArray& data, uint32_t dest
|
||||
|
||||
bool IPC::isCurrentOwner()
|
||||
{
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
if (globalMemory.lock()) {
|
||||
void* data = globalMemory.data();
|
||||
if (!data)
|
||||
{
|
||||
if (!data) {
|
||||
qWarning() << "isCurrentOwner failed to access the memory, returning false";
|
||||
globalMemory.unlock();
|
||||
return false;
|
||||
@ -167,9 +152,7 @@ bool IPC::isCurrentOwner()
|
||||
bool isOwner = ((*(uint64_t*)data) == globalId);
|
||||
globalMemory.unlock();
|
||||
return isOwner;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "isCurrentOwner failed to lock, returning false";
|
||||
return false;
|
||||
}
|
||||
@ -179,7 +162,7 @@ bool IPC::isCurrentOwner()
|
||||
* @brief Register a handler for an IPC event
|
||||
* @param handler The handler callback. Should not block for more than a second, at worst
|
||||
*/
|
||||
void IPC::registerEventHandler(const QString &name, IPCEventHandler handler)
|
||||
void IPC::registerEventHandler(const QString& name, IPCEventHandler handler)
|
||||
{
|
||||
eventHandlers[name] = handler;
|
||||
}
|
||||
@ -187,15 +170,11 @@ void IPC::registerEventHandler(const QString &name, IPCEventHandler handler)
|
||||
bool IPC::isEventAccepted(time_t time)
|
||||
{
|
||||
bool result = false;
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
if (difftime(global()->lastProcessed, time) > 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; ++i) {
|
||||
if (mem->events[i].posted == time && mem->events[i].processed) {
|
||||
result = mem->events[i].accepted;
|
||||
break;
|
||||
}
|
||||
@ -206,7 +185,7 @@ bool IPC::isEventAccepted(time_t time)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout/*=-1*/)
|
||||
bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout /*=-1*/)
|
||||
{
|
||||
bool result = false;
|
||||
time_t start = time(0);
|
||||
@ -226,23 +205,22 @@ bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout/*=-1*/)
|
||||
* @brief Only called when global memory IS LOCKED.
|
||||
* @return nullptr if no evnts present, IPC event otherwise
|
||||
*/
|
||||
IPC::IPCEvent *IPC::fetchEvent()
|
||||
IPC::IPCEvent* IPC::fetchEvent()
|
||||
{
|
||||
IPCMemory* mem = global();
|
||||
for (uint32_t i = 0; i < EVENT_QUEUE_SIZE; ++i)
|
||||
{
|
||||
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))
|
||||
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()
|
||||
&& (evt->dest == Settings::getInstance().getCurrentProfileId()
|
||||
|| (evt->dest == 0 && isCurrentOwner())))
|
||||
&& (evt->dest == Settings::getInstance().getCurrentProfileId()
|
||||
|| (evt->dest == 0 && isCurrentOwner())))
|
||||
return evt;
|
||||
}
|
||||
|
||||
@ -255,10 +233,8 @@ bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg)
|
||||
if (QThread::currentThread() == qApp->thread())
|
||||
result = handler(arg);
|
||||
else
|
||||
QMetaObject::invokeMethod(this, "runEventHandler",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, result),
|
||||
Q_ARG(IPCEventHandler, handler),
|
||||
QMetaObject::invokeMethod(this, "runEventHandler", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, result), Q_ARG(IPCEventHandler, handler),
|
||||
Q_ARG(const QByteArray&, arg));
|
||||
|
||||
return result;
|
||||
@ -266,21 +242,18 @@ bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg)
|
||||
|
||||
void IPC::processEvents()
|
||||
{
|
||||
if (globalMemory.lock())
|
||||
{
|
||||
if (globalMemory.lock()) {
|
||||
IPCMemory* mem = global();
|
||||
|
||||
if (mem->globalId == globalId)
|
||||
{
|
||||
if (mem->globalId == globalId) {
|
||||
// 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() << "Previous owner timed out, taking ownership" << mem->globalId << "->" << globalId;
|
||||
} 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() << "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;
|
||||
@ -289,27 +262,22 @@ void IPC::processEvents()
|
||||
// Non-main instance is limited to events destined for specific profile it runs
|
||||
}
|
||||
|
||||
while (IPCEvent* evt = fetchEvent())
|
||||
{
|
||||
while (IPCEvent* evt = fetchEvent()) {
|
||||
QString name = QString::fromUtf8(evt->name);
|
||||
auto it = eventHandlers.find(name);
|
||||
if (it != eventHandlers.end())
|
||||
{
|
||||
if (it != eventHandlers.end()) {
|
||||
qDebug() << "Processing event: " << name << ":" << evt->posted << "=" << evt->accepted;
|
||||
evt->accepted = runEventHandler(it.value(), evt->data);
|
||||
if (evt->dest == 0)
|
||||
{
|
||||
// Global events should be processed only by instance that accepted event. Otherwise global
|
||||
if (evt->dest == 0) {
|
||||
// Global events should be processed only by instance that accepted event.
|
||||
// Otherwise global
|
||||
// event would be consumed by very first instance that gets to check it.
|
||||
if (evt->accepted)
|
||||
evt->processed = time(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
evt->processed = time(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
globalMemory.unlock();
|
||||
@ -317,7 +285,7 @@ void IPC::processEvents()
|
||||
timer.start();
|
||||
}
|
||||
|
||||
IPC::IPCMemory *IPC::global()
|
||||
IPC::IPCMemory* IPC::global()
|
||||
{
|
||||
return static_cast<IPCMemory*>(globalMemory.data());
|
||||
}
|
||||
|
11
src/ipc.h
11
src/ipc.h
@ -21,15 +21,15 @@
|
||||
#ifndef IPC_H
|
||||
#define IPC_H
|
||||
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QSharedMemory>
|
||||
#include <QTimer>
|
||||
#include <QVector>
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
|
||||
using IPCEventHandler = std::function<bool (const QByteArray&)>;
|
||||
using IPCEventHandler = std::function<bool(const QByteArray&)>;
|
||||
|
||||
#define IPC_PROTOCOL_VERSION "2"
|
||||
|
||||
@ -37,6 +37,7 @@ class IPC : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
IPC();
|
||||
|
||||
protected:
|
||||
static const int EVENT_TIMER_MS = 1000;
|
||||
static const int EVENT_GC_TIMEOUT = 5;
|
||||
@ -69,11 +70,11 @@ public:
|
||||
IPCEvent events[IPC::EVENT_QUEUE_SIZE];
|
||||
};
|
||||
|
||||
time_t postEvent(const QString& name, const QByteArray &data=QByteArray(), uint32_t dest=0);
|
||||
time_t postEvent(const QString& name, const QByteArray& data = QByteArray(), uint32_t dest = 0);
|
||||
bool isCurrentOwner();
|
||||
void registerEventHandler(const QString& name, IPCEventHandler handler);
|
||||
bool isEventAccepted(time_t time);
|
||||
bool waitUntilAccepted(time_t time, int32_t timeout=-1);
|
||||
bool waitUntilAccepted(time_t time, int32_t timeout = -1);
|
||||
|
||||
protected slots:
|
||||
void processEvents();
|
||||
|
142
src/main.cpp
142
src/main.cpp
@ -17,25 +17,25 @@
|
||||
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "widget/widget.h"
|
||||
#include "persistence/settings.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/ipc.h"
|
||||
#include "src/net/toxuri.h"
|
||||
#include "src/net/autoupdate.h"
|
||||
#include "src/persistence/toxsave.h"
|
||||
#include "src/net/toxuri.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#include "src/persistence/toxsave.h"
|
||||
#include "src/video/camerasource.h"
|
||||
#include "src/widget/loginscreen.h"
|
||||
#include "src/widget/translator.h"
|
||||
#include "src/video/camerasource.h"
|
||||
#include "widget/widget.h"
|
||||
#include <QApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QFontDatabase>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
|
||||
#include <sodium.h>
|
||||
@ -47,7 +47,8 @@
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
static QAtomicPointer<FILE> logFileFile = nullptr;
|
||||
static QList<QByteArray>* logBuffer = new QList<QByteArray>(); //Store log messages until log file opened
|
||||
static QList<QByteArray>* logBuffer =
|
||||
new QList<QByteArray>(); // Store log messages until log file opened
|
||||
QMutex* logBufferMutex = new QMutex();
|
||||
#endif
|
||||
|
||||
@ -55,7 +56,7 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
|
||||
{
|
||||
// Silence qWarning spam due to bug in QTextBrowser (trying to open a file for base64 images)
|
||||
if (ctxt.function == QString("virtual bool QFSFileEngine::open(QIODevice::OpenMode)")
|
||||
&& msg == QString("QFSFileEngine::open: No file name specified"))
|
||||
&& msg == QString("QFSFileEngine::open: No file name specified"))
|
||||
return;
|
||||
|
||||
QString file = ctxt.file;
|
||||
@ -63,31 +64,29 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
|
||||
// nullptr in release builds.
|
||||
QString path = QString(__FILE__);
|
||||
path = path.left(path.lastIndexOf('/') + 1);
|
||||
if (file.startsWith(path))
|
||||
{
|
||||
if (file.startsWith(path)) {
|
||||
file = file.mid(path.length());
|
||||
}
|
||||
|
||||
// Time should be in UTC to save user privacy on log sharing
|
||||
QTime time = QDateTime::currentDateTime().toUTC().time();
|
||||
QString LogMsg = QString("[%1 UTC] %2:%3 : ")
|
||||
.arg(time.toString("HH:mm:ss.zzz")).arg(file).arg(ctxt.line);
|
||||
switch (type)
|
||||
{
|
||||
case QtDebugMsg:
|
||||
LogMsg += "Debug";
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
LogMsg += "Warning";
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
LogMsg += "Critical";
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
LogMsg += "Fatal";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
QString LogMsg =
|
||||
QString("[%1 UTC] %2:%3 : ").arg(time.toString("HH:mm:ss.zzz")).arg(file).arg(ctxt.line);
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
LogMsg += "Debug";
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
LogMsg += "Warning";
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
LogMsg += "Critical";
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
LogMsg += "Fatal";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LogMsg += ": " + msg + "\n";
|
||||
@ -95,25 +94,21 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
|
||||
fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), stderr);
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
FILE * logFilePtr = logFileFile.load(); // atomically load the file pointer
|
||||
if (!logFilePtr)
|
||||
{
|
||||
FILE* logFilePtr = logFileFile.load(); // atomically load the file pointer
|
||||
if (!logFilePtr) {
|
||||
logBufferMutex->lock();
|
||||
if (logBuffer)
|
||||
logBuffer->append(LogMsgBytes);
|
||||
|
||||
logBufferMutex->unlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
logBufferMutex->lock();
|
||||
if (logBuffer)
|
||||
{
|
||||
if (logBuffer) {
|
||||
// empty logBuffer to file
|
||||
foreach (QByteArray msg, *logBuffer)
|
||||
fwrite(msg.constData(), 1, msg.size(), logFilePtr);
|
||||
|
||||
delete logBuffer; // no longer needed
|
||||
delete logBuffer; // no longer needed
|
||||
logBuffer = nullptr;
|
||||
}
|
||||
logBufferMutex->unlock();
|
||||
@ -124,7 +119,7 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
@ -141,7 +136,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
#if defined(Q_OS_OSX)
|
||||
// TODO: Add setting to enable this feature.
|
||||
//osx::moveToAppFolder();
|
||||
// osx::moveToAppFolder();
|
||||
osx::migrateProfiles();
|
||||
#endif
|
||||
|
||||
@ -151,11 +146,14 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Process arguments
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION) + "\nBuilt: " + __TIME__ + " " + __DATE__);
|
||||
parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION) + "\nBuilt: "
|
||||
+ __TIME__ + " " + __DATE__);
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse"));
|
||||
parser.addOption(QCommandLineOption("p", QObject::tr("Starts new instance and loads specified profile."), QObject::tr("profile")));
|
||||
parser.addOption(
|
||||
QCommandLineOption("p", QObject::tr("Starts new instance and loads specified profile."),
|
||||
QObject::tr("profile")));
|
||||
parser.process(a);
|
||||
|
||||
IPC& ipc = IPC::getInstance();
|
||||
@ -171,18 +169,17 @@ int main(int argc, char *argv[])
|
||||
QDir(logFileDir).mkpath(".");
|
||||
|
||||
QString logfile = logFileDir + "qtox.log";
|
||||
FILE * mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a");
|
||||
FILE* mainLogFilePtr = fopen(logfile.toLocal8Bit().constData(), "a");
|
||||
|
||||
// Trim log file if over 1MB
|
||||
if (QFileInfo(logfile).size() > 1000000)
|
||||
{
|
||||
if (QFileInfo(logfile).size() > 1000000) {
|
||||
qDebug() << "Log file over 1MB, rotating...";
|
||||
|
||||
// close old logfile (need for windows)
|
||||
if (mainLogFilePtr)
|
||||
fclose(mainLogFilePtr);
|
||||
|
||||
QDir dir (logFileDir);
|
||||
QDir dir(logFileDir);
|
||||
|
||||
// Check if log.1 already exists, and if so, delete it
|
||||
if (dir.remove(logFileDir + "qtox.log.1"))
|
||||
@ -200,7 +197,7 @@ int main(int argc, char *argv[])
|
||||
if (!mainLogFilePtr)
|
||||
qCritical() << "Couldn't open logfile" << logfile;
|
||||
|
||||
logFileFile.store(mainLogFilePtr); // atomically set the logFile
|
||||
logFileFile.store(mainLogFilePtr); // atomically set the logFile
|
||||
#endif
|
||||
|
||||
// Windows platform plugins DLL hell fix
|
||||
@ -213,7 +210,7 @@ int main(int argc, char *argv[])
|
||||
// Install Unicode 6.1 supporting font
|
||||
QFontDatabase::addApplicationFont("://DejaVuSans.ttf");
|
||||
|
||||
// Check whether we have an update waiting to be installed
|
||||
// Check whether we have an update waiting to be installed
|
||||
#if AUTOUPDATE_ENABLED
|
||||
if (AutoUpdater::isLocalUpdateReady())
|
||||
AutoUpdater::installLocalUpdate(); ///< NORETURN
|
||||
@ -229,64 +226,49 @@ int main(int argc, char *argv[])
|
||||
|
||||
uint32_t ipcDest = 0;
|
||||
QString eventType, firstParam;
|
||||
if (parser.isSet("p"))
|
||||
{
|
||||
if (parser.isSet("p")) {
|
||||
profileName = parser.value("p");
|
||||
if (!Profile::exists(profileName))
|
||||
{
|
||||
qCritical() << "-p profile" << profileName + ".tox" << "doesn't exist";
|
||||
if (!Profile::exists(profileName)) {
|
||||
qCritical() << "-p profile" << profileName + ".tox"
|
||||
<< "doesn't exist";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
ipcDest = Settings::makeProfileId(profileName);
|
||||
autoLogin = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
profileName = Settings::getInstance().getCurrentProfile();
|
||||
}
|
||||
|
||||
if (parser.positionalArguments().size() == 0)
|
||||
{
|
||||
if (parser.positionalArguments().size() == 0) {
|
||||
eventType = "activate";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
firstParam = parser.positionalArguments()[0];
|
||||
// Tox URIs. If there's already another qTox instance running, we ask it to handle the URI and we exit
|
||||
// Tox URIs. If there's already another qTox instance running, we ask it to handle the URI
|
||||
// and we exit
|
||||
// Otherwise we start a new qTox instance and process it ourselves
|
||||
if (firstParam.startsWith("tox:"))
|
||||
{
|
||||
if (firstParam.startsWith("tox:")) {
|
||||
eventType = "uri";
|
||||
}
|
||||
else if (firstParam.endsWith(".tox"))
|
||||
{
|
||||
} else if (firstParam.endsWith(".tox")) {
|
||||
eventType = "save";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qCritical() << "Invalid argument";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ipc.isCurrentOwner())
|
||||
{
|
||||
if (!ipc.isCurrentOwner()) {
|
||||
time_t event = ipc.postEvent(eventType, firstParam.toUtf8(), ipcDest);
|
||||
// If someone else processed it, we're done here, no need to actually start qTox
|
||||
if (ipc.waitUntilAccepted(event, 2))
|
||||
{
|
||||
if (ipc.waitUntilAccepted(event, 2)) {
|
||||
qDebug() << "Event" << eventType << "was handled by other client.";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// Autologin
|
||||
if (autoLogin)
|
||||
{
|
||||
if (Profile::exists(profileName))
|
||||
{
|
||||
if (!Profile::isEncrypted(profileName))
|
||||
{
|
||||
if (autoLogin) {
|
||||
if (Profile::exists(profileName)) {
|
||||
if (!Profile::isEncrypted(profileName)) {
|
||||
Profile* profile = Profile::loadProfile(profileName);
|
||||
if (profile)
|
||||
Nexus::getInstance().setProfile(profile);
|
||||
@ -315,7 +297,7 @@ int main(int argc, char *argv[])
|
||||
qDebug() << "Clean exit with status" << errorcode;
|
||||
|
||||
#ifdef LOG_TO_FILE
|
||||
logFileFile.store(nullptr); // atomically disable logging to file
|
||||
logFileFile.store(nullptr); // atomically disable logging to file
|
||||
fclose(mainLogFilePtr);
|
||||
#endif
|
||||
return errorcode;
|
||||
|
@ -18,32 +18,34 @@
|
||||
*/
|
||||
|
||||
#include "src/net/autoupdate.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/persistence/serialize.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/widget/widget.h"
|
||||
#include "src/widget/gui.h"
|
||||
#include "src/nexus.h"
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include "src/widget/widget.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QProcess>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QMutexLocker>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QProcess>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file autoupdate.cpp
|
||||
*
|
||||
* For now we only support auto updates on Windows and OS X, although extending it is not a technical issue.
|
||||
* Linux users are expected to use their package managers or update manually through official channels.
|
||||
* For now we only support auto updates on Windows and OS X, although extending it is not a
|
||||
* technical issue.
|
||||
* Linux users are expected to use their package managers or update manually through official
|
||||
* channels.
|
||||
*/
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
@ -55,22 +57,24 @@ const QString AutoUpdater::platform = "win32";
|
||||
const QString AutoUpdater::updaterBin = "qtox-updater.exe";
|
||||
const QString AutoUpdater::updateServer = "https://qtox-win.pkg.tox.chat";
|
||||
|
||||
unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] =
|
||||
{
|
||||
0x20, 0x89, 0x39, 0xaa, 0x9a, 0xe8, 0xb5, 0x21, 0x0e, 0xac, 0x02, 0xa9, 0xc4, 0x92, 0xd9, 0xa2,
|
||||
0x17, 0x83, 0xbd, 0x78, 0x0a, 0xda, 0x33, 0xcd, 0xa5, 0xc6, 0x44, 0xc7, 0xfc, 0xed, 0x00, 0x13
|
||||
};
|
||||
unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] = {0x20, 0x89, 0x39, 0xaa, 0x9a, 0xe8,
|
||||
0xb5, 0x21, 0x0e, 0xac, 0x02, 0xa9,
|
||||
0xc4, 0x92, 0xd9, 0xa2, 0x17, 0x83,
|
||||
0xbd, 0x78, 0x0a, 0xda, 0x33, 0xcd,
|
||||
0xa5, 0xc6, 0x44, 0xc7, 0xfc, 0xed,
|
||||
0x00, 0x13};
|
||||
|
||||
#elif defined(Q_OS_OSX)
|
||||
const QString AutoUpdater::platform = "osx";
|
||||
const QString AutoUpdater::updaterBin = "/Applications/qtox.app/Contents/MacOS/updater";
|
||||
const QString AutoUpdater::updateServer = "https://dist-build.tox.im";
|
||||
|
||||
unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] =
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00};
|
||||
|
||||
#else
|
||||
const QString AutoUpdater::platform;
|
||||
@ -122,9 +126,12 @@ unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES];
|
||||
* @brief No, we can't just make the QString atomic
|
||||
*/
|
||||
|
||||
const QString AutoUpdater::checkURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/version";
|
||||
const QString AutoUpdater::flistURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/flist";
|
||||
const QString AutoUpdater::filesURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/files/";
|
||||
const QString AutoUpdater::checkURI =
|
||||
AutoUpdater::updateServer + "/qtox/" + AutoUpdater::platform + "/version";
|
||||
const QString AutoUpdater::flistURI =
|
||||
AutoUpdater::updateServer + "/qtox/" + AutoUpdater::platform + "/flist";
|
||||
const QString AutoUpdater::filesURI =
|
||||
AutoUpdater::updateServer + "/qtox/" + AutoUpdater::platform + "/files/";
|
||||
std::atomic_bool AutoUpdater::abortFlag{false};
|
||||
std::atomic_bool AutoUpdater::isDownloadingUpdate{false};
|
||||
std::atomic<float> AutoUpdater::progressValue{0};
|
||||
@ -149,7 +156,8 @@ bool AutoUpdater::isUpdateAvailable()
|
||||
if (isDownloadingUpdate)
|
||||
return false;
|
||||
|
||||
QString updaterPath = updaterBin.startsWith('/') ? updaterBin : qApp->applicationDirPath()+'/'+updaterBin;
|
||||
QString updaterPath =
|
||||
updaterBin.startsWith('/') ? updaterBin : qApp->applicationDirPath() + '/' + updaterBin;
|
||||
if (!QFile::exists(updaterPath))
|
||||
return false;
|
||||
|
||||
@ -175,18 +183,16 @@ AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
|
||||
if (abortFlag)
|
||||
return versionInfo;
|
||||
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager;
|
||||
QNetworkAccessManager* manager = new QNetworkAccessManager;
|
||||
manager->setProxy(Settings::getInstance().getProxy());
|
||||
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(checkURI)));
|
||||
while (!reply->isFinished())
|
||||
{
|
||||
while (!reply->isFinished()) {
|
||||
if (abortFlag)
|
||||
return versionInfo;
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "getUpdateVersion: network error: " + reply->errorString();
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
@ -196,12 +202,11 @@ AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
|
||||
QByteArray data = reply->readAll();
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
if (data.size() < (int)(1+crypto_sign_BYTES))
|
||||
if (data.size() < (int)(1 + crypto_sign_BYTES))
|
||||
return versionInfo;
|
||||
|
||||
// Check updater protocol version
|
||||
if ((int)data[0] != '3')
|
||||
{
|
||||
if ((int)data[0] != '3') {
|
||||
qWarning() << "getUpdateVersion: Bad version " << (uint8_t)data[0];
|
||||
return versionInfo;
|
||||
}
|
||||
@ -209,20 +214,19 @@ AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
|
||||
// Check the signature
|
||||
QByteArray sigData = data.mid(1, crypto_sign_BYTES);
|
||||
unsigned char* sig = (unsigned char*)sigData.data();
|
||||
QByteArray msgData = data.mid(1+crypto_sign_BYTES);
|
||||
QByteArray msgData = data.mid(1 + crypto_sign_BYTES);
|
||||
unsigned char* msg = (unsigned char*)msgData.data();
|
||||
|
||||
if (crypto_sign_verify_detached(sig, msg, msgData.size(), key) != 0)
|
||||
{
|
||||
qCritical() << "getUpdateVersion: RECEIVED FORGED VERSION FILE FROM "<<updateServer;
|
||||
if (crypto_sign_verify_detached(sig, msg, msgData.size(), key) != 0) {
|
||||
qCritical() << "getUpdateVersion: RECEIVED FORGED VERSION FILE FROM " << updateServer;
|
||||
return versionInfo;
|
||||
}
|
||||
|
||||
int sepPos = msgData.indexOf('!');
|
||||
versionInfo.timestamp = QString(msgData.left(sepPos)).toInt();
|
||||
versionInfo.versionString = msgData.mid(sepPos+1);
|
||||
versionInfo.versionString = msgData.mid(sepPos + 1);
|
||||
|
||||
qDebug() << "timestamp:"<<versionInfo.timestamp << ", str:"<<versionInfo.versionString;
|
||||
qDebug() << "timestamp:" << versionInfo.timestamp << ", str:" << versionInfo.versionString;
|
||||
|
||||
return versionInfo;
|
||||
}
|
||||
@ -236,32 +240,27 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseFlist(QByteArray flistData)
|
||||
{
|
||||
QList<UpdateFileMeta> flist;
|
||||
|
||||
if (flistData.isEmpty())
|
||||
{
|
||||
if (flistData.isEmpty()) {
|
||||
qWarning() << "parseflist: Empty data";
|
||||
return flist;
|
||||
}
|
||||
|
||||
// Check version
|
||||
if (flistData[0] != '1')
|
||||
{
|
||||
qWarning() << "parseflist: Bad version "<<(uint8_t)flistData[0];
|
||||
if (flistData[0] != '1') {
|
||||
qWarning() << "parseflist: Bad version " << (uint8_t)flistData[0];
|
||||
return flist;
|
||||
}
|
||||
flistData = flistData.mid(1);
|
||||
|
||||
// Check signature
|
||||
if (flistData.size() < (int)(crypto_sign_BYTES))
|
||||
{
|
||||
if (flistData.size() < (int)(crypto_sign_BYTES)) {
|
||||
qWarning() << "parseflist: Truncated data";
|
||||
return flist;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
QByteArray msgData = flistData.mid(crypto_sign_BYTES);
|
||||
unsigned char* msg = (unsigned char*)msgData.data();
|
||||
if (crypto_sign_verify_detached((unsigned char*)flistData.data(), msg, msgData.size(), key) != 0)
|
||||
{
|
||||
if (crypto_sign_verify_detached((unsigned char*)flistData.data(), msg, msgData.size(), key)
|
||||
!= 0) {
|
||||
qCritical() << "parseflist: FORGED FLIST FILE";
|
||||
return flist;
|
||||
}
|
||||
@ -269,8 +268,7 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseFlist(QByteArray flistData)
|
||||
}
|
||||
|
||||
// Parse. We assume no errors handling needed since the signature is valid.
|
||||
while (!flistData.isEmpty())
|
||||
{
|
||||
while (!flistData.isEmpty()) {
|
||||
UpdateFileMeta newFile;
|
||||
|
||||
memcpy(newFile.sig, flistData.data(), crypto_sign_BYTES);
|
||||
@ -300,18 +298,16 @@ QByteArray AutoUpdater::getUpdateFlist()
|
||||
{
|
||||
QByteArray flist;
|
||||
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager;
|
||||
QNetworkAccessManager* manager = new QNetworkAccessManager;
|
||||
manager->setProxy(Settings::getInstance().getProxy());
|
||||
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(flistURI)));
|
||||
while (!reply->isFinished())
|
||||
{
|
||||
while (!reply->isFinished()) {
|
||||
if (abortFlag)
|
||||
return flist;
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "getUpdateFlist: network error: " + reply->errorString();
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
@ -349,7 +345,7 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::genUpdateDiff(QList<UpdateFileMe
|
||||
bool AutoUpdater::isUpToDate(AutoUpdater::UpdateFileMeta fileMeta)
|
||||
{
|
||||
QString appDir = qApp->applicationDirPath();
|
||||
QFile file(appDir+QDir::separator()+fileMeta.installpath);
|
||||
QFile file(appDir + QDir::separator() + fileMeta.installpath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
|
||||
@ -363,31 +359,31 @@ bool AutoUpdater::isUpToDate(AutoUpdater::UpdateFileMeta fileMeta)
|
||||
|
||||
/**
|
||||
* @brief Tries to fetch the file from the update server.
|
||||
* @note Note that a file with an empty but non-null QByteArray is not an error, merely a file of size 0.
|
||||
* @note Note that a file with an empty but non-null QByteArray is not an error, merely a file of
|
||||
* size 0.
|
||||
* @note Will try to follow qTox's proxy settings, may block and processEvents.
|
||||
* @param fileMeta Meta data fo file to update.
|
||||
* @param progressCallback Callback function, which will connected with QNetworkReply::downloadProgress
|
||||
* @param progressCallback Callback function, which will connected with
|
||||
* QNetworkReply::downloadProgress
|
||||
* @return A file with a null QByteArray on error.
|
||||
*/
|
||||
AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta,
|
||||
std::function<void(int,int)> progressCallback)
|
||||
std::function<void(int, int)> progressCallback)
|
||||
{
|
||||
UpdateFile file;
|
||||
file.metadata = fileMeta;
|
||||
|
||||
QNetworkAccessManager *manager = new QNetworkAccessManager;
|
||||
QNetworkAccessManager* manager = new QNetworkAccessManager;
|
||||
manager->setProxy(Settings::getInstance().getProxy());
|
||||
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(filesURI+fileMeta.id)));
|
||||
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(filesURI + fileMeta.id)));
|
||||
QObject::connect(reply, &QNetworkReply::downloadProgress, progressCallback);
|
||||
while (!reply->isFinished())
|
||||
{
|
||||
while (!reply->isFinished()) {
|
||||
if (abortFlag)
|
||||
return file;
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "getUpdateFile: network error: " + reply->errorString();
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
@ -413,7 +409,7 @@ bool AutoUpdater::downloadUpdate()
|
||||
return false;
|
||||
|
||||
bool expectFalse = false;
|
||||
if (!isDownloadingUpdate.compare_exchange_strong(expectFalse,true))
|
||||
if (!isDownloadingUpdate.compare_exchange_strong(expectFalse, true))
|
||||
return false;
|
||||
|
||||
// Get a list of files to update
|
||||
@ -424,8 +420,7 @@ bool AutoUpdater::downloadUpdate()
|
||||
// Progress
|
||||
progressValue = 0;
|
||||
|
||||
if (abortFlag)
|
||||
{
|
||||
if (abortFlag) {
|
||||
isDownloadingUpdate = false;
|
||||
return false;
|
||||
}
|
||||
@ -438,17 +433,15 @@ bool AutoUpdater::downloadUpdate()
|
||||
if (!updateDir.exists())
|
||||
QDir().mkdir(updateDirStr);
|
||||
updateDir = QDir(updateDirStr);
|
||||
if (!updateDir.exists())
|
||||
{
|
||||
if (!updateDir.exists()) {
|
||||
qWarning() << "downloadUpdate: Can't create update directory, aborting...";
|
||||
isDownloadingUpdate = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the new flist for the updater
|
||||
QFile newFlistFile(updateDirStr+"flist");
|
||||
if (!newFlistFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
{
|
||||
QFile newFlistFile(updateDirStr + "flist");
|
||||
if (!newFlistFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
qWarning() << "downloadUpdate: Can't save new flist file, aborting...";
|
||||
isDownloadingUpdate = false;
|
||||
return false;
|
||||
@ -459,25 +452,21 @@ bool AutoUpdater::downloadUpdate()
|
||||
progressValue = 1;
|
||||
|
||||
// Download and write each new file
|
||||
for (UpdateFileMeta fileMeta : diff)
|
||||
{
|
||||
float initialProgress = progressValue, step = 99./diff.size();
|
||||
auto stepProgressCallback = [&](int current, int total)
|
||||
{
|
||||
progressValue = initialProgress + step * (float)current/total;
|
||||
for (UpdateFileMeta fileMeta : diff) {
|
||||
float initialProgress = progressValue, step = 99. / diff.size();
|
||||
auto stepProgressCallback = [&](int current, int total) {
|
||||
progressValue = initialProgress + step * (float)current / total;
|
||||
};
|
||||
|
||||
if (abortFlag)
|
||||
{
|
||||
if (abortFlag) {
|
||||
isDownloadingUpdate = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip files we already have
|
||||
QFile fileFile(updateDirStr+fileMeta.installpath);
|
||||
if (fileFile.open(QIODevice::ReadOnly) && fileFile.size() == (qint64)fileMeta.size)
|
||||
{
|
||||
qDebug() << "Skipping already downloaded file '" + fileMeta.installpath+ "'";
|
||||
QFile fileFile(updateDirStr + fileMeta.installpath);
|
||||
if (fileFile.open(QIODevice::ReadOnly) && fileFile.size() == (qint64)fileMeta.size) {
|
||||
qDebug() << "Skipping already downloaded file '" + fileMeta.installpath + "'";
|
||||
progressValue = initialProgress + step;
|
||||
continue;
|
||||
}
|
||||
@ -486,7 +475,7 @@ bool AutoUpdater::downloadUpdate()
|
||||
qDebug() << "Downloading '" + fileMeta.installpath + "' ...";
|
||||
|
||||
// Create subdirs if necessary
|
||||
QString fileDirStr{QFileInfo(updateDirStr+fileMeta.installpath).absolutePath()};
|
||||
QString fileDirStr{QFileInfo(updateDirStr + fileMeta.installpath).absolutePath()};
|
||||
if (!QDir(fileDirStr).exists())
|
||||
QDir().mkpath(fileDirStr);
|
||||
|
||||
@ -494,23 +483,21 @@ bool AutoUpdater::downloadUpdate()
|
||||
UpdateFile file = getUpdateFile(fileMeta, stepProgressCallback);
|
||||
if (abortFlag)
|
||||
goto fail;
|
||||
if (file.data.isNull())
|
||||
{
|
||||
if (file.data.isNull()) {
|
||||
qCritical() << "downloadUpdate: Error downloading a file, aborting...";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Check signature
|
||||
if (crypto_sign_verify_detached(file.metadata.sig, (unsigned char*)file.data.data(),
|
||||
file.data.size(), key) != 0)
|
||||
{
|
||||
file.data.size(), key)
|
||||
!= 0) {
|
||||
qCritical() << "downloadUpdate: RECEIVED FORGED FILE, aborting...";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Save
|
||||
if (!fileFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
{
|
||||
if (!fileFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
qCritical() << "downloadUpdate: Can't save new update file, aborting...";
|
||||
goto fail;
|
||||
}
|
||||
@ -556,7 +543,7 @@ bool AutoUpdater::isLocalUpdateReady()
|
||||
return false;
|
||||
|
||||
// Check that we have a flist and generate a diff
|
||||
QFile updateFlistFile(updateDirStr+"flist");
|
||||
QFile updateFlistFile(updateDirStr + "flist");
|
||||
if (!updateFlistFile.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
QByteArray updateFlistData = updateFlistFile.readAll();
|
||||
@ -566,12 +553,11 @@ bool AutoUpdater::isLocalUpdateReady()
|
||||
QList<UpdateFileMeta> diff = genUpdateDiff(updateFlist);
|
||||
|
||||
// Check that we have every file
|
||||
for (UpdateFileMeta fileMeta : diff)
|
||||
{
|
||||
if (!QFile::exists(updateDirStr+fileMeta.installpath))
|
||||
for (UpdateFileMeta fileMeta : diff) {
|
||||
if (!QFile::exists(updateDirStr + fileMeta.installpath))
|
||||
return false;
|
||||
|
||||
QFile f(updateDirStr+fileMeta.installpath);
|
||||
QFile f(updateDirStr + fileMeta.installpath);
|
||||
if (f.size() != (int64_t)fileMeta.size)
|
||||
return false;
|
||||
}
|
||||
@ -591,8 +577,7 @@ void AutoUpdater::installLocalUpdate()
|
||||
qDebug() << "About to start the qTox updater to install a local update";
|
||||
|
||||
// Prepare to delete the update if we fail so we don't fail again.
|
||||
auto failExit = []()
|
||||
{
|
||||
auto failExit = []() {
|
||||
qCritical() << "Failed to start the qTox updater, removing the update and exiting";
|
||||
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
|
||||
QDir(updateDirStr).removeRecursively();
|
||||
@ -603,17 +588,16 @@ void AutoUpdater::installLocalUpdate()
|
||||
if (platform.isEmpty())
|
||||
failExit();
|
||||
|
||||
// Workaround QTBUG-7645
|
||||
// QProcess fails silently when elevation is required instead of showing a UAC prompt on Win7/Vista
|
||||
// Workaround QTBUG-7645
|
||||
// QProcess fails silently when elevation is required instead of showing a UAC prompt on Win7/Vista
|
||||
#ifdef Q_OS_WIN
|
||||
QString modulePath = qApp->applicationDirPath().replace('/', '\\');
|
||||
HINSTANCE result = ::ShellExecuteW(0, L"open", updaterBin.toStdWString().c_str(),
|
||||
0, modulePath.toStdWString().c_str(), SW_SHOWNORMAL);
|
||||
if (result == (HINSTANCE)SE_ERR_ACCESSDENIED)
|
||||
{
|
||||
HINSTANCE result = ::ShellExecuteW(0, L"open", updaterBin.toStdWString().c_str(), 0,
|
||||
modulePath.toStdWString().c_str(), SW_SHOWNORMAL);
|
||||
if (result == (HINSTANCE)SE_ERR_ACCESSDENIED) {
|
||||
// Requesting elevation
|
||||
result = ::ShellExecuteW(0, L"runas", updaterBin.toStdWString().c_str(),
|
||||
0, modulePath.toStdWString().c_str(), SW_SHOWNORMAL);
|
||||
result = ::ShellExecuteW(0, L"runas", updaterBin.toStdWString().c_str(), 0,
|
||||
modulePath.toStdWString().c_str(), SW_SHOWNORMAL);
|
||||
}
|
||||
if (result <= (HINSTANCE)32)
|
||||
failExit();
|
||||
@ -656,9 +640,7 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
|
||||
QDir updateDir(updateDirStr);
|
||||
|
||||
|
||||
|
||||
if (updateDir.exists() && QFile(updateDirStr+"flist").exists())
|
||||
{
|
||||
if (updateDir.exists() && QFile(updateDirStr + "flist").exists()) {
|
||||
setProgressVersion(getUpdateVersion().versionString);
|
||||
downloadUpdate();
|
||||
return;
|
||||
@ -668,16 +650,17 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
|
||||
QString contentText = QObject::tr("An update is available, do you want to download it now?\n"
|
||||
"It will be installed when qTox restarts.");
|
||||
if (!newVersion.versionString.isEmpty())
|
||||
contentText += "\n\n" + QObject::tr("Version %1, %2").arg(newVersion.versionString,
|
||||
QDateTime::fromMSecsSinceEpoch(newVersion.timestamp*1000).toString());
|
||||
contentText +=
|
||||
"\n\n"
|
||||
+ QObject::tr("Version %1, %2")
|
||||
.arg(newVersion.versionString,
|
||||
QDateTime::fromMSecsSinceEpoch(newVersion.timestamp * 1000).toString());
|
||||
|
||||
|
||||
if (abortFlag)
|
||||
return;
|
||||
|
||||
if (GUI::askQuestion(QObject::tr("Update", "The title of a message box"),
|
||||
contentText, true, false))
|
||||
{
|
||||
if (GUI::askQuestion(QObject::tr("Update", "The title of a message box"), contentText, true, false)) {
|
||||
setProgressVersion(newVersion.versionString);
|
||||
GUI::showUpdateDownloadProgress();
|
||||
downloadUpdate();
|
||||
|
@ -21,12 +21,12 @@
|
||||
#ifndef AUTOUPDATE_H
|
||||
#define AUTOUPDATE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <sodium.h>
|
||||
#include <QString>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <sodium.h>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#define AUTOUPDATE_ENABLED 1
|
||||
@ -48,8 +48,7 @@ public:
|
||||
|
||||
bool operator==(const UpdateFileMeta& other)
|
||||
{
|
||||
return (size == other.size
|
||||
&& id == other.id && installpath == other.installpath
|
||||
return (size == other.size && id == other.id && installpath == other.installpath
|
||||
&& memcmp(sig, other.sig, crypto_sign_BYTES) == 0);
|
||||
}
|
||||
};
|
||||
@ -72,7 +71,7 @@ public:
|
||||
static VersionInfo getUpdateVersion();
|
||||
static bool downloadUpdate();
|
||||
static bool isLocalUpdateReady();
|
||||
[[ noreturn ]] static void installLocalUpdate();
|
||||
[[noreturn]] static void installLocalUpdate();
|
||||
static void abortUpdates();
|
||||
static QString getProgressVersion();
|
||||
static int getProgressValue();
|
||||
@ -82,7 +81,8 @@ protected:
|
||||
static QByteArray getUpdateFlist();
|
||||
static QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist);
|
||||
static bool isUpToDate(UpdateFileMeta file);
|
||||
static UpdateFile getUpdateFile(UpdateFileMeta fileMeta, std::function<void(int,int)> progressCallback);
|
||||
static UpdateFile getUpdateFile(UpdateFileMeta fileMeta,
|
||||
std::function<void(int, int)> progressCallback);
|
||||
static void checkUpdatesAsyncInteractiveWorker();
|
||||
static void setProgressVersion(QString version);
|
||||
|
||||
|
@ -20,8 +20,8 @@
|
||||
|
||||
#include "avatarbroadcaster.h"
|
||||
#include "src/core/core.h"
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
|
||||
/**
|
||||
* @class AvatarBroadcaster
|
||||
@ -35,8 +35,7 @@ QByteArray AvatarBroadcaster::avatarData;
|
||||
QMap<uint32_t, bool> AvatarBroadcaster::friendsSentTo;
|
||||
|
||||
static QMetaObject::Connection autoBroadcastConn;
|
||||
static auto autoBroadcast = [](uint32_t friendId, Status)
|
||||
{
|
||||
static auto autoBroadcast = [](uint32_t friendId, Status) {
|
||||
AvatarBroadcaster::sendAvatarTo(friendId);
|
||||
};
|
||||
|
||||
@ -79,5 +78,6 @@ void AvatarBroadcaster::enableAutoBroadcast(bool state)
|
||||
{
|
||||
QObject::disconnect(autoBroadcastConn);
|
||||
if (state)
|
||||
autoBroadcastConn = QObject::connect(Core::getInstance(), &Core::friendStatusChanged, autoBroadcast);
|
||||
autoBroadcastConn =
|
||||
QObject::connect(Core::getInstance(), &Core::friendStatusChanged, autoBroadcast);
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
class AvatarBroadcaster
|
||||
{
|
||||
private:
|
||||
AvatarBroadcaster()=delete;
|
||||
AvatarBroadcaster() = delete;
|
||||
|
||||
public:
|
||||
static void setAvatar(QByteArray data);
|
||||
|
@ -19,16 +19,16 @@
|
||||
|
||||
#include "toxme.h"
|
||||
#include "src/core/core.h"
|
||||
#include <src/persistence/settings.h>
|
||||
#include <QtDebug>
|
||||
#include <QCoreApplication>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
#include <QtDebug>
|
||||
#include <ctime>
|
||||
#include <sodium/crypto_box.h>
|
||||
#include <sodium/randombytes.h>
|
||||
#include <src/persistence/settings.h>
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
|
||||
/**
|
||||
* @class Toxme
|
||||
@ -38,7 +38,7 @@
|
||||
* @note May process events while waiting for blocking calls
|
||||
*/
|
||||
|
||||
QByteArray Toxme::makeJsonRequest(QString url, QString json, QNetworkReply::NetworkError &error)
|
||||
QByteArray Toxme::makeJsonRequest(QString url, QString json, QNetworkReply::NetworkError& error)
|
||||
{
|
||||
if (error)
|
||||
return QByteArray();
|
||||
@ -49,8 +49,7 @@ QByteArray Toxme::makeJsonRequest(QString url, QString json, QNetworkReply::Netw
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QNetworkReply* reply = netman.post(request, json.toUtf8());
|
||||
|
||||
while (!reply->isFinished())
|
||||
{
|
||||
while (!reply->isFinished()) {
|
||||
QThread::msleep(1);
|
||||
qApp->processEvents();
|
||||
}
|
||||
@ -60,7 +59,7 @@ QByteArray Toxme::makeJsonRequest(QString url, QString json, QNetworkReply::Netw
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray Toxme::getServerPubkey(QString url, QNetworkReply::NetworkError &error)
|
||||
QByteArray Toxme::getServerPubkey(QString url, QNetworkReply::NetworkError& error)
|
||||
{
|
||||
if (error)
|
||||
return QByteArray();
|
||||
@ -72,15 +71,13 @@ QByteArray Toxme::getServerPubkey(QString url, QNetworkReply::NetworkError &erro
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QNetworkReply* reply = netman.get(request);
|
||||
|
||||
while (!reply->isFinished())
|
||||
{
|
||||
while (!reply->isFinished()) {
|
||||
QThread::msleep(1);
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
error = reply->error();
|
||||
if (error)
|
||||
{
|
||||
if (error) {
|
||||
qWarning() << "getServerPubkey: A network error occured:" << reply->errorString();
|
||||
return QByteArray();
|
||||
}
|
||||
@ -94,12 +91,12 @@ QByteArray Toxme::getServerPubkey(QString url, QNetworkReply::NetworkError &erro
|
||||
int start = json.indexOf(pattern) + pattern.length();
|
||||
int end = json.indexOf("\"", start);
|
||||
int pubkeySize = (end - start) / 2;
|
||||
QString rawKey = json.mid(start, pubkeySize*2);
|
||||
QString rawKey = json.mid(start, pubkeySize * 2);
|
||||
|
||||
QByteArray key;
|
||||
// I think, exist more easy way to convert key to ByteArray
|
||||
for (int i = 0; i < pubkeySize; ++i) {
|
||||
QString byte = rawKey.mid(i*2, 2);
|
||||
QString byte = rawKey.mid(i * 2, 2);
|
||||
key[i] = byte.toInt(nullptr, 16);
|
||||
}
|
||||
|
||||
@ -109,8 +106,7 @@ QByteArray Toxme::getServerPubkey(QString url, QNetworkReply::NetworkError &erro
|
||||
QByteArray Toxme::prepareEncryptedJson(QString url, int action, QString payload)
|
||||
{
|
||||
QPair<QByteArray, QByteArray> keypair = Core::getInstance()->getKeypair();
|
||||
if (keypair.first.isEmpty() || keypair.second.isEmpty())
|
||||
{
|
||||
if (keypair.first.isEmpty() || keypair.second.isEmpty()) {
|
||||
qWarning() << "prepareEncryptedJson: Couldn't get our keypair, aborting";
|
||||
return QByteArray();
|
||||
}
|
||||
@ -124,12 +120,12 @@ QByteArray Toxme::prepareEncryptedJson(QString url, int action, QString payload)
|
||||
randombytes((uint8_t*)nonce.data(), crypto_box_NONCEBYTES);
|
||||
|
||||
QByteArray payloadData = payload.toUtf8();
|
||||
const size_t cypherlen = crypto_box_MACBYTES+payloadData.size();
|
||||
const size_t cypherlen = crypto_box_MACBYTES + payloadData.size();
|
||||
unsigned char* payloadEnc = new unsigned char[cypherlen];
|
||||
|
||||
int cryptResult = crypto_box_easy(payloadEnc,(uint8_t*)payloadData.data(),payloadData.size(),
|
||||
(uint8_t*)nonce.data(),(unsigned char*)key.constData(),
|
||||
(uint8_t*)keypair.second.data());
|
||||
int cryptResult = crypto_box_easy(payloadEnc, (uint8_t*)payloadData.data(), payloadData.size(),
|
||||
(uint8_t*)nonce.data(), (unsigned char*)key.constData(),
|
||||
(uint8_t*)keypair.second.data());
|
||||
|
||||
if (cryptResult != 0) // error
|
||||
return QByteArray();
|
||||
@ -137,10 +133,13 @@ QByteArray Toxme::prepareEncryptedJson(QString url, int action, QString payload)
|
||||
QByteArray payloadEncData(reinterpret_cast<char*>(payloadEnc), cypherlen);
|
||||
delete[] payloadEnc;
|
||||
|
||||
const QString json{"{\"action\":"+QString().setNum(action)+","
|
||||
"\"public_key\":\""+keypair.first.toHex()+"\","
|
||||
"\"encrypted\":\""+payloadEncData.toBase64()+"\","
|
||||
"\"nonce\":\""+nonce.toBase64()+"\"}"};
|
||||
const QString json{"{\"action\":" + QString().setNum(action) + ","
|
||||
"\"public_key\":\""
|
||||
+ keypair.first.toHex() + "\","
|
||||
"\"encrypted\":\""
|
||||
+ payloadEncData.toBase64() + "\","
|
||||
"\"nonce\":\""
|
||||
+ nonce.toBase64() + "\"}"};
|
||||
return json.toUtf8();
|
||||
}
|
||||
|
||||
@ -153,10 +152,10 @@ ToxId Toxme::lookup(QString address)
|
||||
{
|
||||
// JSON injection ?
|
||||
address = address.trimmed();
|
||||
address.replace('\\',"\\\\");
|
||||
address.replace('"',"\"");
|
||||
address.replace('\\', "\\\\");
|
||||
address.replace('"', "\"");
|
||||
|
||||
const QString json{"{\"action\":3,\"name\":\""+address+"\"}"};
|
||||
const QString json{"{\"action\":3,\"name\":\"" + address + "\"}"};
|
||||
|
||||
QString apiUrl = "https://" + address.split(QLatin1Char('@')).last() + "/api";
|
||||
QNetworkReply::NetworkError error = QNetworkReply::NoError;
|
||||
@ -170,13 +169,13 @@ ToxId Toxme::lookup(QString address)
|
||||
if (index == -1)
|
||||
return ToxId();
|
||||
|
||||
response = response.mid(index+pattern.size());
|
||||
response = response.mid(index + pattern.size());
|
||||
|
||||
const int idStart = response.indexOf('"');
|
||||
if (idStart == -1)
|
||||
return ToxId();
|
||||
|
||||
response = response.mid(idStart+1);
|
||||
response = response.mid(idStart + 1);
|
||||
|
||||
const int idEnd = response.indexOf('"');
|
||||
if (idEnd == -1)
|
||||
@ -199,10 +198,9 @@ Toxme::ExecCode Toxme::extractError(QString json)
|
||||
if (start == -1)
|
||||
return ServerError;
|
||||
|
||||
json = json.mid(start+pattern.size());
|
||||
json = json.mid(start + pattern.size());
|
||||
int end = json.indexOf(",");
|
||||
if (end == -1)
|
||||
{
|
||||
if (end == -1) {
|
||||
end = json.indexOf("}");
|
||||
if (end == -1)
|
||||
return IncorrectResponse;
|
||||
@ -227,16 +225,16 @@ Toxme::ExecCode Toxme::extractError(QString json)
|
||||
* @param[in] bio A short optional description of yourself if you want to publish your address.
|
||||
* @return password on success, else sets code parameter and returns an empty QString.
|
||||
*/
|
||||
QString Toxme::createAddress(ExecCode &code, QString server, ToxId id, QString address,
|
||||
QString Toxme::createAddress(ExecCode& code, QString server, ToxId id, QString address,
|
||||
bool keepPrivate, QString bio)
|
||||
{
|
||||
int privacy = keepPrivate ? 0 : 2;
|
||||
// JSON injection ?
|
||||
bio.replace('\\',"\\\\");
|
||||
bio.replace('"',"\"");
|
||||
bio.replace('\\', "\\\\");
|
||||
bio.replace('"', "\"");
|
||||
|
||||
address.replace('\\',"\\\\");
|
||||
address.replace('"',"\"");
|
||||
address.replace('\\', "\\\\");
|
||||
address.replace('"', "\"");
|
||||
|
||||
bio = bio.trimmed();
|
||||
address = address.trimmed();
|
||||
@ -244,14 +242,18 @@ QString Toxme::createAddress(ExecCode &code, QString server, ToxId id, QString a
|
||||
if (!server.contains("://"))
|
||||
server = "https://" + server;
|
||||
|
||||
const QString payload{"{\"tox_id\":\""+id.toString()+"\","
|
||||
"\"name\":\""+address+"\","
|
||||
"\"privacy\":"+QString().setNum(privacy)+","
|
||||
"\"bio\":\""+bio+"\","
|
||||
"\"timestamp\":"+QString().setNum(time(0))+"}"};
|
||||
const QString payload{"{\"tox_id\":\"" + id.toString() + "\","
|
||||
"\"name\":\""
|
||||
+ address + "\","
|
||||
"\"privacy\":"
|
||||
+ QString().setNum(privacy) + ","
|
||||
"\"bio\":\""
|
||||
+ bio + "\","
|
||||
"\"timestamp\":"
|
||||
+ QString().setNum(time(0)) + "}"};
|
||||
|
||||
QString pubkeyUrl = server + "/pk";
|
||||
QString apiUrl = server + "/api";
|
||||
QString apiUrl = server + "/api";
|
||||
QNetworkReply::NetworkError error = QNetworkReply::NoError;
|
||||
QByteArray encrypted = prepareEncryptedJson(pubkeyUrl, 1, payload);
|
||||
QByteArray response = makeJsonRequest(apiUrl, encrypted, error);
|
||||
@ -263,28 +265,26 @@ QString Toxme::createAddress(ExecCode &code, QString server, ToxId id, QString a
|
||||
return getPass(response, code);
|
||||
}
|
||||
|
||||
QString Toxme::getPass(QString json, ExecCode &code) {
|
||||
QString Toxme::getPass(QString json, ExecCode& code)
|
||||
{
|
||||
static const QByteArray pattern{"password\":"};
|
||||
|
||||
json = json.remove(' ');
|
||||
const int start = json.indexOf(pattern);
|
||||
if (start == -1)
|
||||
{
|
||||
if (start == -1) {
|
||||
code = NoPassword;
|
||||
return QString();
|
||||
}
|
||||
|
||||
json = json.mid(start+pattern.size());
|
||||
if (json.startsWith("null"))
|
||||
{
|
||||
json = json.mid(start + pattern.size());
|
||||
if (json.startsWith("null")) {
|
||||
code = Updated;
|
||||
return QString();
|
||||
}
|
||||
|
||||
json = json.mid(1, json.length());
|
||||
int end = json.indexOf("\"");
|
||||
if (end == -1)
|
||||
{
|
||||
if (end == -1) {
|
||||
code = IncorrectResponse;
|
||||
return QString();
|
||||
}
|
||||
@ -302,8 +302,9 @@ QString Toxme::getPass(QString json, ExecCode &code) {
|
||||
*/
|
||||
Toxme::ExecCode Toxme::deleteAddress(QString server, ToxPk id)
|
||||
{
|
||||
const QString payload{"{\"public_key\":\""+id.toString()+"\","
|
||||
"\"timestamp\":"+QString().setNum(time(0))+"}"};
|
||||
const QString payload{"{\"public_key\":\"" + id.toString() + "\","
|
||||
"\"timestamp\":"
|
||||
+ QString().setNum(time(0)) + "}"};
|
||||
|
||||
server = server.trimmed();
|
||||
if (!server.contains("://"))
|
||||
|
@ -21,19 +21,20 @@
|
||||
#ifndef TOXME_H
|
||||
#define TOXME_H
|
||||
|
||||
#include <QString>
|
||||
#include "src/core/toxid.h"
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QNetworkReply>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include "src/core/toxid.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
|
||||
class Toxme
|
||||
{
|
||||
public:
|
||||
enum ExecCode {
|
||||
enum ExecCode
|
||||
{
|
||||
ExecError = -50,
|
||||
Ok = 0,
|
||||
Updated = 1,
|
||||
@ -43,18 +44,18 @@ public:
|
||||
};
|
||||
|
||||
static ToxId lookup(QString address);
|
||||
static QString createAddress(ExecCode &code, QString server, ToxId id, QString address,
|
||||
bool keepPrivate=true, QString bio=QString());
|
||||
static QString createAddress(ExecCode& code, QString server, ToxId id, QString address,
|
||||
bool keepPrivate = true, QString bio = QString());
|
||||
static ExecCode deleteAddress(QString server, ToxPk id);
|
||||
static QString getErrorMessage(int errorCode);
|
||||
static QString translateErrorMessage(int errorCode);
|
||||
|
||||
private:
|
||||
Toxme() = delete;
|
||||
static QByteArray makeJsonRequest(QString url, QString json, QNetworkReply::NetworkError &error);
|
||||
static QByteArray makeJsonRequest(QString url, QString json, QNetworkReply::NetworkError& error);
|
||||
static QByteArray prepareEncryptedJson(QString url, int action, QString payload);
|
||||
static QByteArray getServerPubkey(QString url, QNetworkReply::NetworkError &error);
|
||||
static QString getPass(QString json, ExecCode &code);
|
||||
static QByteArray getServerPubkey(QString url, QNetworkReply::NetworkError& error);
|
||||
static QString getPass(QString json, ExecCode& code);
|
||||
static ExecCode extractError(QString json);
|
||||
|
||||
private:
|
||||
|
@ -19,21 +19,21 @@
|
||||
|
||||
|
||||
#include "src/net/toxuri.h"
|
||||
#include "src/net/toxme.h"
|
||||
#include "src/widget/tool/friendrequestdialog.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/net/toxme.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/widget/tool/friendrequestdialog.h"
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QMessageBox>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCoreApplication>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
bool toxURIEventHandler(const QByteArray& eventData)
|
||||
{
|
||||
@ -50,13 +50,12 @@ bool toxURIEventHandler(const QByteArray& eventData)
|
||||
* @param toxURI Tox URI to try to add.
|
||||
* @return True, if tox URI is correct, false otherwise.
|
||||
*/
|
||||
bool handleToxURI(const QString &toxURI)
|
||||
bool handleToxURI(const QString& toxURI)
|
||||
{
|
||||
Nexus& nexus = Nexus::getInstance();
|
||||
Core* core = nexus.getCore();
|
||||
|
||||
while (!core)
|
||||
{
|
||||
while (!core) {
|
||||
if (!nexus.isRunning())
|
||||
return false;
|
||||
|
||||
@ -65,8 +64,7 @@ bool handleToxURI(const QString &toxURI)
|
||||
QThread::msleep(10);
|
||||
}
|
||||
|
||||
while (!core->isReady())
|
||||
{
|
||||
while (!core->isReady()) {
|
||||
if (!nexus.isRunning())
|
||||
return false;
|
||||
|
||||
@ -77,15 +75,13 @@ bool handleToxURI(const QString &toxURI)
|
||||
QString toxaddr = toxURI.mid(4);
|
||||
|
||||
ToxId toxId(toxaddr);
|
||||
if (!toxId.isValid())
|
||||
{
|
||||
if (!toxId.isValid()) {
|
||||
toxId = Toxme::lookup(toxaddr);
|
||||
if (!toxId.isValid())
|
||||
{
|
||||
QMessageBox *messageBox = new QMessageBox(QMessageBox::Warning,
|
||||
QMessageBox::tr("Couldn't add friend"),
|
||||
QMessageBox::tr("%1 is not a valid Toxme address.")
|
||||
.arg(toxaddr), QMessageBox::Ok, nullptr);
|
||||
if (!toxId.isValid()) {
|
||||
QMessageBox* messageBox =
|
||||
new QMessageBox(QMessageBox::Warning, QMessageBox::tr("Couldn't add friend"),
|
||||
QMessageBox::tr("%1 is not a valid Toxme address.").arg(toxaddr),
|
||||
QMessageBox::Ok, nullptr);
|
||||
messageBox->setButtonText(QMessageBox::Ok, QMessageBox::tr("Ok"));
|
||||
QObject::connect(messageBox, &QMessageBox::finished, messageBox, &QMessageBox::deleteLater);
|
||||
messageBox->show();
|
||||
@ -93,25 +89,26 @@ bool handleToxURI(const QString &toxURI)
|
||||
}
|
||||
}
|
||||
|
||||
if (toxId == core->getSelfId())
|
||||
{
|
||||
QMessageBox *messageBox = new QMessageBox(QMessageBox::Warning,
|
||||
QMessageBox::tr("Couldn't add friend"),
|
||||
QMessageBox::tr("You can't add yourself as a friend!",
|
||||
"When trying to add your own Tox ID as friend"),
|
||||
QMessageBox::Ok, nullptr);
|
||||
if (toxId == core->getSelfId()) {
|
||||
QMessageBox* messageBox =
|
||||
new QMessageBox(QMessageBox::Warning, QMessageBox::tr("Couldn't add friend"),
|
||||
QMessageBox::tr("You can't add yourself as a friend!",
|
||||
"When trying to add your own Tox ID as friend"),
|
||||
QMessageBox::Ok, nullptr);
|
||||
messageBox->setButtonText(QMessageBox::Ok, QMessageBox::tr("Ok"));
|
||||
QObject::connect(messageBox, &QMessageBox::finished, messageBox, &QMessageBox::deleteLater);
|
||||
messageBox->show();
|
||||
return false;
|
||||
}
|
||||
|
||||
ToxURIDialog *dialog = new ToxURIDialog(0, toxaddr, QObject::tr("%1 here! Tox me maybe?",
|
||||
"Default message in Tox URI friend requests. Write something appropriate!")
|
||||
.arg(Nexus::getCore()->getUsername()));
|
||||
ToxURIDialog* dialog = new ToxURIDialog(
|
||||
0, toxaddr,
|
||||
QObject::tr("%1 here! Tox me maybe?",
|
||||
"Default message in Tox URI friend requests. Write something appropriate!")
|
||||
.arg(Nexus::getCore()->getUsername()));
|
||||
QObject::connect(dialog, &ToxURIDialog::finished, [=](int result) {
|
||||
if (result == QDialog::Accepted)
|
||||
Core::getInstance()->requestFriendship(toxId, dialog->getRequestMessage());
|
||||
Core::getInstance()->requestFriendship(toxId, dialog->getRequestMessage());
|
||||
|
||||
dialog->deleteLater();
|
||||
});
|
||||
@ -120,21 +117,21 @@ bool handleToxURI(const QString &toxURI)
|
||||
return true;
|
||||
}
|
||||
|
||||
ToxURIDialog::ToxURIDialog(QWidget* parent, const QString &userId, const QString &message) :
|
||||
QDialog(parent)
|
||||
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);
|
||||
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);
|
||||
QLabel* messageLabel = new QLabel(tr("Friend request message:"), this);
|
||||
messageEdit = new QPlainTextEdit(message, this);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal, 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);
|
||||
@ -142,7 +139,7 @@ ToxURIDialog::ToxURIDialog(QWidget* parent, const QString &userId, const QString
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &FriendRequestDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &FriendRequestDialog::reject);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
|
||||
layout->addWidget(friendsLabel);
|
||||
layout->addSpacing(12);
|
||||
|
@ -33,11 +33,11 @@ class ToxURIDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ToxURIDialog(QWidget* parent, const QString &userId, const QString &message);
|
||||
explicit ToxURIDialog(QWidget* parent, const QString& userId, const QString& message);
|
||||
QString getRequestMessage();
|
||||
|
||||
private:
|
||||
QPlainTextEdit *messageEdit;
|
||||
QPlainTextEdit* messageEdit;
|
||||
};
|
||||
|
||||
#endif // TOXURI_H
|
||||
|
113
src/nexus.cpp
113
src/nexus.cpp
@ -19,28 +19,28 @@
|
||||
|
||||
|
||||
#include "nexus.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#include "persistence/settings.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/core/coreav.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#include "src/widget/widget.h"
|
||||
#include "persistence/settings.h"
|
||||
#include "video/camerasource.h"
|
||||
#include "widget/gui.h"
|
||||
#include "widget/loginscreen.h"
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include <QImageReader>
|
||||
#include <QFile>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFile>
|
||||
#include <QImageReader>
|
||||
#include <QThread>
|
||||
#include <cassert>
|
||||
#include <vpx/vpx_image.h>
|
||||
#include <QDesktopWidget>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include <QWindow>
|
||||
#include <QMenuBar>
|
||||
#include <QActionGroup>
|
||||
#include <QMenuBar>
|
||||
#include <QSignalMapper>
|
||||
#include <QWindow>
|
||||
#endif
|
||||
|
||||
/**
|
||||
@ -55,13 +55,13 @@ Q_DECLARE_OPAQUE_POINTER(ToxAV*)
|
||||
|
||||
static Nexus* nexus{nullptr};
|
||||
|
||||
Nexus::Nexus(QObject *parent) :
|
||||
QObject(parent),
|
||||
profile{nullptr},
|
||||
widget{nullptr},
|
||||
loginScreen{nullptr},
|
||||
running{true},
|
||||
quitOnLastWindowClosed{true}
|
||||
Nexus::Nexus(QObject* parent)
|
||||
: QObject(parent)
|
||||
, profile{nullptr}
|
||||
, widget{nullptr}
|
||||
, loginScreen{nullptr}
|
||||
, running{true}
|
||||
, quitOnLastWindowClosed{true}
|
||||
{
|
||||
}
|
||||
|
||||
@ -132,8 +132,7 @@ void Nexus::start()
|
||||
|
||||
minimizeAction = windowMenu->addAction(QString());
|
||||
minimizeAction->setShortcut(Qt::CTRL + Qt::Key_M);
|
||||
connect(minimizeAction, &QAction::triggered, [this]()
|
||||
{
|
||||
connect(minimizeAction, &QAction::triggered, [this]() {
|
||||
minimizeAction->setEnabled(false);
|
||||
QApplication::focusWindow()->showMinimized();
|
||||
});
|
||||
@ -172,7 +171,8 @@ void Nexus::showLogin()
|
||||
profile = nullptr;
|
||||
|
||||
loginScreen->reset();
|
||||
loginScreen->move(QApplication::desktop()->screen()->rect().center() - loginScreen->rect().center());
|
||||
loginScreen->move(QApplication::desktop()->screen()->rect().center()
|
||||
- loginScreen->rect().center());
|
||||
loginScreen->show();
|
||||
quitOnLastWindowClosed = true;
|
||||
}
|
||||
@ -199,35 +199,36 @@ void Nexus::showMainGUI()
|
||||
|
||||
// Connections
|
||||
Core* core = profile->getCore();
|
||||
connect(core, &Core::connected, widget, &Widget::onConnected);
|
||||
connect(core, &Core::disconnected, widget, &Widget::onDisconnected);
|
||||
connect(core, &Core::failedToStart, widget, &Widget::onFailedToStartCore, Qt::BlockingQueuedConnection);
|
||||
connect(core, &Core::badProxy, widget, &Widget::onBadProxyCore, Qt::BlockingQueuedConnection);
|
||||
connect(core, &Core::statusSet, widget, &Widget::onStatusSet);
|
||||
connect(core, &Core::usernameSet, widget, &Widget::setUsername);
|
||||
connect(core, &Core::statusMessageSet, widget, &Widget::setStatusMessage);
|
||||
connect(core, &Core::selfAvatarChanged, widget, &Widget::onSelfAvatarLoaded);
|
||||
connect(core, &Core::friendAdded, widget, &Widget::addFriend);
|
||||
connect(core, &Core::friendshipChanged, widget, &Widget::onFriendshipChanged);
|
||||
connect(core, &Core::failedToAddFriend, widget, &Widget::addFriendFailed);
|
||||
connect(core, &Core::friendUsernameChanged, widget, &Widget::onFriendUsernameChanged);
|
||||
connect(core, &Core::friendStatusChanged, widget, &Widget::onFriendStatusChanged);
|
||||
connect(core, &Core::connected, widget, &Widget::onConnected);
|
||||
connect(core, &Core::disconnected, widget, &Widget::onDisconnected);
|
||||
connect(core, &Core::failedToStart, widget, &Widget::onFailedToStartCore,
|
||||
Qt::BlockingQueuedConnection);
|
||||
connect(core, &Core::badProxy, widget, &Widget::onBadProxyCore, Qt::BlockingQueuedConnection);
|
||||
connect(core, &Core::statusSet, widget, &Widget::onStatusSet);
|
||||
connect(core, &Core::usernameSet, widget, &Widget::setUsername);
|
||||
connect(core, &Core::statusMessageSet, widget, &Widget::setStatusMessage);
|
||||
connect(core, &Core::selfAvatarChanged, widget, &Widget::onSelfAvatarLoaded);
|
||||
connect(core, &Core::friendAdded, widget, &Widget::addFriend);
|
||||
connect(core, &Core::friendshipChanged, widget, &Widget::onFriendshipChanged);
|
||||
connect(core, &Core::failedToAddFriend, widget, &Widget::addFriendFailed);
|
||||
connect(core, &Core::friendUsernameChanged, widget, &Widget::onFriendUsernameChanged);
|
||||
connect(core, &Core::friendStatusChanged, widget, &Widget::onFriendStatusChanged);
|
||||
connect(core, &Core::friendStatusMessageChanged, widget, &Widget::onFriendStatusMessageChanged);
|
||||
connect(core, &Core::friendRequestReceived, widget, &Widget::onFriendRequestReceived);
|
||||
connect(core, &Core::friendMessageReceived, widget, &Widget::onFriendMessageReceived);
|
||||
connect(core, &Core::receiptRecieved, widget, &Widget::onReceiptRecieved);
|
||||
connect(core, &Core::groupInviteReceived, widget, &Widget::onGroupInviteReceived);
|
||||
connect(core, &Core::groupMessageReceived, widget, &Widget::onGroupMessageReceived);
|
||||
connect(core, &Core::groupNamelistChanged, widget, &Widget::onGroupNamelistChanged);
|
||||
connect(core, &Core::groupTitleChanged, widget, &Widget::onGroupTitleChanged);
|
||||
connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying);
|
||||
connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated);
|
||||
connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged);
|
||||
connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult);
|
||||
connect(core, &Core::groupSentResult, widget, &Widget::onGroupSendResult);
|
||||
connect(core, &Core::friendRequestReceived, widget, &Widget::onFriendRequestReceived);
|
||||
connect(core, &Core::friendMessageReceived, widget, &Widget::onFriendMessageReceived);
|
||||
connect(core, &Core::receiptRecieved, widget, &Widget::onReceiptRecieved);
|
||||
connect(core, &Core::groupInviteReceived, widget, &Widget::onGroupInviteReceived);
|
||||
connect(core, &Core::groupMessageReceived, widget, &Widget::onGroupMessageReceived);
|
||||
connect(core, &Core::groupNamelistChanged, widget, &Widget::onGroupNamelistChanged);
|
||||
connect(core, &Core::groupTitleChanged, widget, &Widget::onGroupTitleChanged);
|
||||
connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying);
|
||||
connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated);
|
||||
connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged);
|
||||
connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult);
|
||||
connect(core, &Core::groupSentResult, widget, &Widget::onGroupSendResult);
|
||||
|
||||
connect(widget, &Widget::statusSet, core, &Core::setStatus);
|
||||
connect(widget, &Widget::friendRequested, core, &Core::requestFriendship);
|
||||
connect(widget, &Widget::statusSet, core, &Core::setStatus);
|
||||
connect(widget, &Widget::friendRequested, core, &Core::requestFriendship);
|
||||
connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest);
|
||||
|
||||
profile->startCore();
|
||||
@ -314,11 +315,11 @@ Widget* Nexus::getDesktopGUI()
|
||||
|
||||
QString Nexus::getSupportedImageFilter()
|
||||
{
|
||||
QString res;
|
||||
for (auto type : QImageReader::supportedImageFormats())
|
||||
res += QString("*.%1 ").arg(QString(type));
|
||||
QString res;
|
||||
for (auto type : QImageReader::supportedImageFormats())
|
||||
res += QString("*.%1 ").arg(QString(type));
|
||||
|
||||
return tr("Images (%1)", "filetype filter").arg(res.left(res.size()-1));
|
||||
return tr("Images (%1)", "filetype filter").arg(res.left(res.size() - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -362,8 +363,7 @@ void Nexus::onWindowStateChanged(Qt::WindowStates state)
|
||||
{
|
||||
minimizeAction->setEnabled(QApplication::activeWindow() != nullptr);
|
||||
|
||||
if (QApplication::activeWindow() != nullptr && sender() == QApplication::activeWindow())
|
||||
{
|
||||
if (QApplication::activeWindow() != nullptr && sender() == QApplication::activeWindow()) {
|
||||
if (state & Qt::WindowFullScreen)
|
||||
minimizeAction->setEnabled(false);
|
||||
|
||||
@ -404,8 +404,7 @@ void Nexus::updateWindowsArg(QWindow* closedWindow)
|
||||
else
|
||||
activeWindow = nullptr;
|
||||
|
||||
for (int i = 0; i < windowList.size(); ++i)
|
||||
{
|
||||
for (int i = 0; i < windowList.size(); ++i) {
|
||||
if (closedWindow == windowList[i])
|
||||
continue;
|
||||
|
||||
@ -432,10 +431,8 @@ void Nexus::updateWindowsStates()
|
||||
bool exists = false;
|
||||
QWindowList windowList = QApplication::topLevelWindows();
|
||||
|
||||
for (QWindow* window : windowList)
|
||||
{
|
||||
if (!(window->windowState() & Qt::WindowMinimized))
|
||||
{
|
||||
for (QWindow* window : windowList) {
|
||||
if (!(window->windowState() & Qt::WindowMinimized)) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ public slots:
|
||||
void bringAllToFront();
|
||||
|
||||
private:
|
||||
void updateWindowsArg(QWindow *closedWindow);
|
||||
void updateWindowsArg(QWindow* closedWindow);
|
||||
|
||||
QSignalMapper* windowMapper;
|
||||
QActionGroup* windowActions = nullptr;
|
||||
@ -90,7 +90,7 @@ private slots:
|
||||
void onLastWindowClosed();
|
||||
|
||||
private:
|
||||
explicit Nexus(QObject *parent = 0);
|
||||
explicit Nexus(QObject* parent = 0);
|
||||
~Nexus();
|
||||
|
||||
private:
|
||||
|
@ -42,7 +42,8 @@
|
||||
* @brief Implements a low level RAII interface to a SQLCipher (SQlite3) database.
|
||||
*
|
||||
* Thread-safe, does all database operations on a worker thread.
|
||||
* The queries must not contain transaction commands (BEGIN/COMMIT/...) or the behavior is undefined.
|
||||
* The queries must not contain transaction commands (BEGIN/COMMIT/...) or the behavior is
|
||||
* undefined.
|
||||
*
|
||||
* @var QMutex RawDatabase::transactionsMutex;
|
||||
* @brief Protects pendingTransactions
|
||||
@ -94,7 +95,7 @@
|
||||
RawDatabase::RawDatabase(const QString& path, const QString& password, const QByteArray& salt)
|
||||
: workerThread{new QThread}
|
||||
, path{path}
|
||||
, currentSalt{salt} // we need the salt later if a new password should be set
|
||||
, currentSalt{salt} // we need the salt later if a new password should be set
|
||||
, currentHexKey{deriveKey(password, salt)}
|
||||
{
|
||||
workerThread->setObjectName("qTox Database");
|
||||
@ -102,8 +103,7 @@ RawDatabase::RawDatabase(const QString& path, const QString& password, const QBy
|
||||
workerThread->start();
|
||||
|
||||
// first try with the new salt
|
||||
if (open(path, currentHexKey))
|
||||
{
|
||||
if (open(path, currentHexKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -112,32 +112,24 @@ RawDatabase::RawDatabase(const QString& path, const QString& password, const QBy
|
||||
|
||||
// create a backup before trying to upgrade to new salt
|
||||
bool upgrade = true;
|
||||
if(!QFile::copy(path, path + ".bak"))
|
||||
{
|
||||
if (!QFile::copy(path, path + ".bak")) {
|
||||
qDebug() << "Couldn't create the backup of the database, won't upgrade";
|
||||
upgrade = false;
|
||||
}
|
||||
|
||||
// fall back to the old salt
|
||||
currentHexKey = deriveKey(password);
|
||||
if(open(path, currentHexKey))
|
||||
{
|
||||
if (open(path, currentHexKey)) {
|
||||
// upgrade only if backup successful
|
||||
if(upgrade)
|
||||
{
|
||||
if (upgrade) {
|
||||
// still using old salt, upgrade
|
||||
if(setPassword(password))
|
||||
{
|
||||
if (setPassword(password)) {
|
||||
qDebug() << "Successfully upgraded to dynamic salt";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "Failed to set password with new salt";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qDebug() << "Failed to open database with old salt";
|
||||
}
|
||||
}
|
||||
@ -156,40 +148,36 @@ RawDatabase::~RawDatabase()
|
||||
* @param hexKey Hex representation of the key in string.
|
||||
* @return True if success, false otherwise.
|
||||
*/
|
||||
bool RawDatabase::open(const QString& path, const QString &hexKey)
|
||||
bool RawDatabase::open(const QString& path, const QString& hexKey)
|
||||
{
|
||||
if (QThread::currentThread() != workerThread.get())
|
||||
{
|
||||
if (QThread::currentThread() != workerThread.get()) {
|
||||
bool ret;
|
||||
QMetaObject::invokeMethod(this, "open", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret),
|
||||
Q_ARG(const QString&, path), Q_ARG(const QString&, hexKey));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!QFile::exists(path) && QFile::exists(path+".tmp"))
|
||||
{
|
||||
qWarning() << "Restoring database from temporary export file! Did we crash while changing the password?";
|
||||
QFile::rename(path+".tmp", path);
|
||||
if (!QFile::exists(path) && QFile::exists(path + ".tmp")) {
|
||||
qWarning() << "Restoring database from temporary export file! Did we crash while changing "
|
||||
"the password?";
|
||||
QFile::rename(path + ".tmp", path);
|
||||
}
|
||||
|
||||
if (sqlite3_open_v2(path.toUtf8().data(), &sqlite,
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr) != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Failed to open database"<<path<<"with error:"<<sqlite3_errmsg(sqlite);
|
||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr)
|
||||
!= SQLITE_OK) {
|
||||
qWarning() << "Failed to open database" << path << "with error:" << sqlite3_errmsg(sqlite);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hexKey.isEmpty())
|
||||
{
|
||||
if (!execNow("PRAGMA key = \"x'"+hexKey+"'\""))
|
||||
{
|
||||
if (!hexKey.isEmpty()) {
|
||||
if (!execNow("PRAGMA key = \"x'" + hexKey + "'\"")) {
|
||||
qWarning() << "Failed to set encryption key";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!execNow("SELECT count(*) FROM sqlite_master"))
|
||||
{
|
||||
if (!execNow("SELECT count(*) FROM sqlite_master")) {
|
||||
qWarning() << "Database is unusable, check that the password is correct";
|
||||
close();
|
||||
return false;
|
||||
@ -212,7 +200,7 @@ void RawDatabase::close()
|
||||
if (sqlite3_close(sqlite) == SQLITE_OK)
|
||||
sqlite = nullptr;
|
||||
else
|
||||
qWarning() << "Error closing database:"<<sqlite3_errmsg(sqlite);
|
||||
qWarning() << "Error closing database:" << sqlite3_errmsg(sqlite);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,7 +228,7 @@ bool RawDatabase::execNow(const QString& statement)
|
||||
* @param statement Statement to execute.
|
||||
* @return Whether the transaction was successful.
|
||||
*/
|
||||
bool RawDatabase::execNow(const RawDatabase::Query &statement)
|
||||
bool RawDatabase::execNow(const RawDatabase::Query& statement)
|
||||
{
|
||||
return execNow(QVector<Query>{statement});
|
||||
}
|
||||
@ -250,10 +238,9 @@ bool RawDatabase::execNow(const RawDatabase::Query &statement)
|
||||
* @param statements List of statements to execute.
|
||||
* @return Whether the transaction was successful.
|
||||
*/
|
||||
bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements)
|
||||
bool RawDatabase::execNow(const QVector<RawDatabase::Query>& statements)
|
||||
{
|
||||
if (!sqlite)
|
||||
{
|
||||
if (!sqlite) {
|
||||
qWarning() << "Trying to exec, but the database is not open";
|
||||
return false;
|
||||
}
|
||||
@ -283,20 +270,19 @@ bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements)
|
||||
* @brief Executes a SQL transaction asynchronously.
|
||||
* @param statement Statement to execute.
|
||||
*/
|
||||
void RawDatabase::execLater(const QString &statement)
|
||||
void RawDatabase::execLater(const QString& statement)
|
||||
{
|
||||
execLater(Query{statement});
|
||||
}
|
||||
|
||||
void RawDatabase::execLater(const RawDatabase::Query &statement)
|
||||
void RawDatabase::execLater(const RawDatabase::Query& statement)
|
||||
{
|
||||
execLater(QVector<Query>{statement});
|
||||
}
|
||||
|
||||
void RawDatabase::execLater(const QVector<RawDatabase::Query> &statements)
|
||||
void RawDatabase::execLater(const QVector<RawDatabase::Query>& statements)
|
||||
{
|
||||
if (!sqlite)
|
||||
{
|
||||
if (!sqlite) {
|
||||
qWarning() << "Trying to exec, but the database is not open";
|
||||
return;
|
||||
}
|
||||
@ -327,17 +313,15 @@ void RawDatabase::sync()
|
||||
*/
|
||||
bool RawDatabase::setPassword(const QString& password)
|
||||
{
|
||||
if (!sqlite)
|
||||
{
|
||||
if (!sqlite) {
|
||||
qWarning() << "Trying to change the password, but the database is not open";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() != workerThread.get())
|
||||
{
|
||||
if (QThread::currentThread() != workerThread.get()) {
|
||||
bool ret;
|
||||
QMetaObject::invokeMethod(this, "setPassword", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, password));
|
||||
Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, password));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -345,59 +329,50 @@ bool RawDatabase::setPassword(const QString& password)
|
||||
// so we always process the pending queue before rekeying for consistency
|
||||
process();
|
||||
|
||||
if (QFile::exists(path+".tmp"))
|
||||
{
|
||||
if (QFile::exists(path + ".tmp")) {
|
||||
qWarning() << "Found old temporary export file while rekeying, deleting it";
|
||||
QFile::remove(path+".tmp");
|
||||
QFile::remove(path + ".tmp");
|
||||
}
|
||||
|
||||
if (!password.isEmpty())
|
||||
{
|
||||
if (!password.isEmpty()) {
|
||||
QString newHexKey = deriveKey(password, currentSalt);
|
||||
if (!currentHexKey.isEmpty())
|
||||
{
|
||||
if (!execNow("PRAGMA rekey = \"x'"+newHexKey+"'\""))
|
||||
{
|
||||
if (!currentHexKey.isEmpty()) {
|
||||
if (!execNow("PRAGMA rekey = \"x'" + newHexKey + "'\"")) {
|
||||
qWarning() << "Failed to change encryption key";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Need to encrypt the database
|
||||
if (!execNow("ATTACH DATABASE '"+path+".tmp' AS encrypted KEY \"x'"+newHexKey+"'\";"
|
||||
"SELECT sqlcipher_export('encrypted');"
|
||||
"DETACH DATABASE encrypted;"))
|
||||
{
|
||||
if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS encrypted KEY \"x'" + newHexKey
|
||||
+ "'\";"
|
||||
"SELECT sqlcipher_export('encrypted');"
|
||||
"DETACH DATABASE encrypted;")) {
|
||||
qWarning() << "Failed to export encrypted database";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is racy as hell, but nobody will race with us since we hold the profile lock
|
||||
// If we crash or die here, the rename should be atomic, so we can recover no matter what
|
||||
// If we crash or die here, the rename should be atomic, so we can recover no matter
|
||||
// what
|
||||
close();
|
||||
QFile::remove(path);
|
||||
QFile::rename(path+".tmp", path);
|
||||
QFile::rename(path + ".tmp", path);
|
||||
currentHexKey = newHexKey;
|
||||
if (!open(path, currentHexKey))
|
||||
{
|
||||
if (!open(path, currentHexKey)) {
|
||||
qWarning() << "Failed to open encrypted database";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if (currentHexKey.isEmpty())
|
||||
return true;
|
||||
|
||||
// Need to decrypt the database
|
||||
if (!execNow("ATTACH DATABASE '"+path+".tmp' AS plaintext KEY '';"
|
||||
"SELECT sqlcipher_export('plaintext');"
|
||||
"DETACH DATABASE plaintext;"))
|
||||
{
|
||||
if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS plaintext KEY '';"
|
||||
"SELECT sqlcipher_export('plaintext');"
|
||||
"DETACH DATABASE plaintext;")) {
|
||||
qWarning() << "Failed to export decrypted database";
|
||||
close();
|
||||
return false;
|
||||
@ -407,10 +382,9 @@ bool RawDatabase::setPassword(const QString& password)
|
||||
// If we crash or die here, the rename should be atomic, so we can recover no matter what
|
||||
close();
|
||||
QFile::remove(path);
|
||||
QFile::rename(path+".tmp", path);
|
||||
QFile::rename(path + ".tmp", path);
|
||||
currentHexKey.clear();
|
||||
if (!open(path))
|
||||
{
|
||||
if (!open(path)) {
|
||||
qCritical() << "Failed to open decrypted database";
|
||||
return false;
|
||||
}
|
||||
@ -425,19 +399,17 @@ bool RawDatabase::setPassword(const QString& password)
|
||||
*
|
||||
* @note Will process all transactions before renaming
|
||||
*/
|
||||
bool RawDatabase::rename(const QString &newPath)
|
||||
bool RawDatabase::rename(const QString& newPath)
|
||||
{
|
||||
if (!sqlite)
|
||||
{
|
||||
if (!sqlite) {
|
||||
qWarning() << "Trying to change the password, but the database is not open";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() != workerThread.get())
|
||||
{
|
||||
if (QThread::currentThread() != workerThread.get()) {
|
||||
bool ret;
|
||||
QMetaObject::invokeMethod(this, "rename", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, newPath));
|
||||
Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, newPath));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -463,20 +435,19 @@ bool RawDatabase::rename(const QString &newPath)
|
||||
*/
|
||||
bool RawDatabase::remove()
|
||||
{
|
||||
if (!sqlite)
|
||||
{
|
||||
if (!sqlite) {
|
||||
qWarning() << "Trying to remove the database, but it is not open";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() != workerThread.get())
|
||||
{
|
||||
if (QThread::currentThread() != workerThread.get()) {
|
||||
bool ret;
|
||||
QMetaObject::invokeMethod(this, "remove", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret));
|
||||
QMetaObject::invokeMethod(this, "remove", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
qDebug() << "Removing database "<< path;
|
||||
qDebug() << "Removing database " << path;
|
||||
close();
|
||||
return QFile::remove(path);
|
||||
}
|
||||
@ -489,7 +460,8 @@ bool RawDatabase::remove()
|
||||
*/
|
||||
struct PassKeyDeleter
|
||||
{
|
||||
void operator()(Tox_Pass_Key *pass_key) {
|
||||
void operator()(Tox_Pass_Key* pass_key)
|
||||
{
|
||||
tox_pass_key_free(pass_key);
|
||||
}
|
||||
};
|
||||
@ -500,7 +472,7 @@ struct PassKeyDeleter
|
||||
* @return String representation of key
|
||||
* @deprecated deprecated on 2016-11-06, kept for compatibility, replaced by the salted version
|
||||
*/
|
||||
QString RawDatabase::deriveKey(const QString &password)
|
||||
QString RawDatabase::deriveKey(const QString& password)
|
||||
{
|
||||
if (password.isEmpty())
|
||||
return {};
|
||||
@ -509,11 +481,13 @@ QString RawDatabase::deriveKey(const QString &password)
|
||||
|
||||
static_assert(TOX_PASS_KEY_LENGTH >= 32, "toxcore must provide 256bit or longer keys");
|
||||
|
||||
static const uint8_t expandConstant[TOX_PASS_SALT_LENGTH+1] = "L'ignorance est le pire des maux";
|
||||
static const uint8_t expandConstant[TOX_PASS_SALT_LENGTH + 1] =
|
||||
"L'ignorance est le pire des maux";
|
||||
std::unique_ptr<Tox_Pass_Key, PassKeyDeleter> key(tox_pass_key_new());
|
||||
tox_pass_key_derive_with_salt(key.get(), reinterpret_cast<uint8_t*>(passData.data()),
|
||||
static_cast<std::size_t>(passData.size()), expandConstant, nullptr);
|
||||
return QByteArray(reinterpret_cast<char*>(key.get()) + 32, 32).toHex();;
|
||||
return QByteArray(reinterpret_cast<char*>(key.get()) + 32, 32).toHex();
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -524,13 +498,11 @@ QString RawDatabase::deriveKey(const QString &password)
|
||||
*/
|
||||
QString RawDatabase::deriveKey(const QString& password, const QByteArray& salt)
|
||||
{
|
||||
if (password.isEmpty())
|
||||
{
|
||||
if (password.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (salt.length() != TOX_PASS_SALT_LENGTH)
|
||||
{
|
||||
if (salt.length() != TOX_PASS_SALT_LENGTH) {
|
||||
qWarning() << "Salt length doesn't match toxencryptsave expections";
|
||||
return {};
|
||||
}
|
||||
@ -543,7 +515,8 @@ QString RawDatabase::deriveKey(const QString& password, const QByteArray& salt)
|
||||
tox_pass_key_derive_with_salt(key.get(), reinterpret_cast<uint8_t*>(passData.data()),
|
||||
static_cast<std::size_t>(passData.size()),
|
||||
reinterpret_cast<const uint8_t*>(salt.constData()), nullptr);
|
||||
return QByteArray(reinterpret_cast<char*>(key.get()) + 32, 32).toHex();;
|
||||
return QByteArray(reinterpret_cast<char*>(key.get()) + 32, 32).toHex();
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -575,28 +548,27 @@ void RawDatabase::process()
|
||||
trans.success->store(false, std::memory_order_release);
|
||||
|
||||
// Add transaction commands if necessary
|
||||
if (trans.queries.size() > 1)
|
||||
{
|
||||
if (trans.queries.size() > 1) {
|
||||
trans.queries.prepend({"BEGIN;"});
|
||||
trans.queries.append({"COMMIT;"});
|
||||
}
|
||||
|
||||
// Compile queries
|
||||
for (Query& query : trans.queries)
|
||||
{
|
||||
for (Query& query : trans.queries) {
|
||||
assert(query.statements.isEmpty());
|
||||
// sqlite3_prepare_v2 only compiles one statement at a time in the query,
|
||||
// we need to loop over them all
|
||||
int curParam=0;
|
||||
int curParam = 0;
|
||||
const char* compileTail = query.query.data();
|
||||
do {
|
||||
// Compile the next statement
|
||||
sqlite3_stmt* stmt;
|
||||
int r;
|
||||
if ((r = sqlite3_prepare_v2(sqlite, compileTail,
|
||||
query.query.size() - static_cast<int>(compileTail - query.query.data()),
|
||||
&stmt, &compileTail)) != SQLITE_OK)
|
||||
{
|
||||
query.query.size()
|
||||
- static_cast<int>(compileTail - query.query.data()),
|
||||
&stmt, &compileTail))
|
||||
!= SQLITE_OK) {
|
||||
qWarning() << "Failed to prepare statement" << anonymizeQuery(query.query)
|
||||
<< "with error" << r;
|
||||
goto cleanupStatements;
|
||||
@ -605,39 +577,34 @@ void RawDatabase::process()
|
||||
|
||||
// Now we can bind our params to this statement
|
||||
int nParams = sqlite3_bind_parameter_count(stmt);
|
||||
if (query.blobs.size() < curParam+nParams)
|
||||
{
|
||||
if (query.blobs.size() < curParam + nParams) {
|
||||
qWarning() << "Not enough parameters to bind to query "
|
||||
<< anonymizeQuery(query.query);
|
||||
goto cleanupStatements;
|
||||
}
|
||||
for (int i=0; i<nParams; ++i)
|
||||
{
|
||||
const QByteArray& blob = query.blobs[curParam+i];
|
||||
if (sqlite3_bind_blob(stmt, i+1, blob.data(), blob.size(), SQLITE_STATIC) != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Failed to bind param" << curParam + i
|
||||
<< "to query" << anonymizeQuery(query.query);
|
||||
for (int i = 0; i < nParams; ++i) {
|
||||
const QByteArray& blob = query.blobs[curParam + i];
|
||||
if (sqlite3_bind_blob(stmt, i + 1, blob.data(), blob.size(), SQLITE_STATIC)
|
||||
!= SQLITE_OK) {
|
||||
qWarning() << "Failed to bind param" << curParam + i << "to query"
|
||||
<< anonymizeQuery(query.query);
|
||||
goto cleanupStatements;
|
||||
}
|
||||
}
|
||||
curParam += nParams;
|
||||
} while (compileTail != query.query.data()+query.query.size());
|
||||
} while (compileTail != query.query.data() + query.query.size());
|
||||
}
|
||||
|
||||
// Execute each statement of each query of our transaction
|
||||
for (Query& query : trans.queries)
|
||||
{
|
||||
for (sqlite3_stmt* stmt : query.statements)
|
||||
{
|
||||
for (Query& query : trans.queries) {
|
||||
for (sqlite3_stmt* stmt : query.statements) {
|
||||
int column_count = sqlite3_column_count(stmt);
|
||||
int result;
|
||||
do {
|
||||
result = sqlite3_step(stmt);
|
||||
|
||||
// Execute our row callback
|
||||
if (result == SQLITE_ROW && query.rowCallback)
|
||||
{
|
||||
if (result == SQLITE_ROW && query.rowCallback) {
|
||||
QVector<QVariant> row;
|
||||
for (int i = 0; i < column_count; ++i)
|
||||
row += extractData(stmt, i);
|
||||
@ -661,8 +628,7 @@ void RawDatabase::process()
|
||||
qWarning() << "Constraint error executing query" << anonQuery;
|
||||
goto cleanupStatements;
|
||||
default:
|
||||
qWarning() << "Unknown error" << result
|
||||
<< "executing query" << anonQuery;
|
||||
qWarning() << "Unknown error" << result << "executing query" << anonQuery;
|
||||
goto cleanupStatements;
|
||||
}
|
||||
}
|
||||
@ -674,10 +640,9 @@ void RawDatabase::process()
|
||||
if (trans.success != nullptr)
|
||||
trans.success->store(true, std::memory_order_release);
|
||||
|
||||
// Free our statements
|
||||
cleanupStatements:
|
||||
for (Query& query : trans.queries)
|
||||
{
|
||||
// Free our statements
|
||||
cleanupStatements:
|
||||
for (Query& query : trans.queries) {
|
||||
for (sqlite3_stmt* stmt : query.statements)
|
||||
sqlite3_finalize(stmt);
|
||||
query.statements.clear();
|
||||
@ -711,25 +676,18 @@ QString RawDatabase::anonymizeQuery(const QByteArray& query)
|
||||
* @param col Number of column to extract.
|
||||
* @return Extracted data.
|
||||
*/
|
||||
QVariant RawDatabase::extractData(sqlite3_stmt *stmt, int col)
|
||||
QVariant RawDatabase::extractData(sqlite3_stmt* stmt, int col)
|
||||
{
|
||||
int type = sqlite3_column_type(stmt, col);
|
||||
if (type == SQLITE_INTEGER)
|
||||
{
|
||||
if (type == SQLITE_INTEGER) {
|
||||
return sqlite3_column_int64(stmt, col);
|
||||
}
|
||||
else if (type == SQLITE_TEXT)
|
||||
{
|
||||
} else if (type == SQLITE_TEXT) {
|
||||
const char* str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, col));
|
||||
int len = sqlite3_column_bytes(stmt, col);
|
||||
return QString::fromUtf8(str, len);
|
||||
}
|
||||
else if (type == SQLITE_NULL)
|
||||
{
|
||||
} else if (type == SQLITE_NULL) {
|
||||
return QVariant{};
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
const char* data = reinterpret_cast<const char*>(sqlite3_column_blob(stmt, col));
|
||||
int len = sqlite3_column_bytes(stmt, col);
|
||||
return QByteArray::fromRawData(data, len);
|
||||
|
@ -1,16 +1,16 @@
|
||||
#ifndef RAWDATABASE_H
|
||||
#define RAWDATABASE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QThread>
|
||||
#include <QQueue>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#include <QMutex>
|
||||
#include <QPair>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <QVariant>
|
||||
#include <memory>
|
||||
#include <QVector>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
struct sqlite3;
|
||||
struct sqlite3_stmt;
|
||||
@ -23,13 +23,25 @@ public:
|
||||
class Query
|
||||
{
|
||||
public:
|
||||
Query(QString query, QVector<QByteArray> blobs = {}, std::function<void(int64_t)> insertCallback={})
|
||||
: query{query.toUtf8()}, blobs{blobs}, insertCallback{insertCallback} {}
|
||||
Query(QString query, QVector<QByteArray> blobs = {},
|
||||
std::function<void(int64_t)> insertCallback = {})
|
||||
: query{query.toUtf8()}
|
||||
, blobs{blobs}
|
||||
, insertCallback{insertCallback}
|
||||
{
|
||||
}
|
||||
Query(QString query, std::function<void(int64_t)> insertCallback)
|
||||
: query{query.toUtf8()}, insertCallback{insertCallback} {}
|
||||
: query{query.toUtf8()}
|
||||
, insertCallback{insertCallback}
|
||||
{
|
||||
}
|
||||
Query(QString query, std::function<void(const QVector<QVariant>&)> rowCallback)
|
||||
: query{query.toUtf8()}, rowCallback{rowCallback} {}
|
||||
: query{query.toUtf8()}
|
||||
, rowCallback{rowCallback}
|
||||
{
|
||||
}
|
||||
Query() = default;
|
||||
|
||||
private:
|
||||
QByteArray query;
|
||||
QVector<QByteArray> blobs;
|
||||
@ -64,6 +76,7 @@ protected slots:
|
||||
bool open(const QString& path, const QString& hexKey = {});
|
||||
void close();
|
||||
void process();
|
||||
|
||||
private:
|
||||
QString anonymizeQuery(const QByteArray& query);
|
||||
|
||||
|
@ -20,10 +20,10 @@
|
||||
#include <QDebug>
|
||||
#include <cassert>
|
||||
|
||||
#include "db/rawdatabase.h"
|
||||
#include "history.h"
|
||||
#include "profile.h"
|
||||
#include "settings.h"
|
||||
#include "db/rawdatabase.h"
|
||||
|
||||
/**
|
||||
* @class History
|
||||
@ -41,31 +41,31 @@
|
||||
History::History(std::shared_ptr<RawDatabase> db)
|
||||
: db(db)
|
||||
{
|
||||
if (!isValid())
|
||||
{
|
||||
if (!isValid()) {
|
||||
qWarning() << "Database not open, init failed";
|
||||
return;
|
||||
}
|
||||
|
||||
db->execLater("CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL UNIQUE);"
|
||||
"CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY, owner INTEGER,"
|
||||
"display_name BLOB NOT NULL, UNIQUE(owner, display_name));"
|
||||
"CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, "
|
||||
"chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, "
|
||||
"message BLOB NOT NULL);"
|
||||
"CREATE TABLE IF NOT EXISTS faux_offline_pending (id INTEGER PRIMARY KEY);");
|
||||
db->execLater(
|
||||
"CREATE TABLE IF NOT EXISTS peers (id INTEGER PRIMARY KEY, public_key TEXT NOT NULL "
|
||||
"UNIQUE);"
|
||||
"CREATE TABLE IF NOT EXISTS aliases (id INTEGER PRIMARY KEY, owner INTEGER,"
|
||||
"display_name BLOB NOT NULL, UNIQUE(owner, display_name));"
|
||||
"CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, "
|
||||
"chat_id INTEGER NOT NULL, sender_alias INTEGER NOT NULL, "
|
||||
"message BLOB NOT NULL);"
|
||||
"CREATE TABLE IF NOT EXISTS faux_offline_pending (id INTEGER PRIMARY KEY);");
|
||||
|
||||
// Cache our current peers
|
||||
db->execLater(RawDatabase::Query{"SELECT public_key, id FROM peers;", [this](const QVector<QVariant>& row)
|
||||
{
|
||||
peers[row[0].toString()] = row[1].toInt();
|
||||
}});
|
||||
db->execLater(RawDatabase::Query{"SELECT public_key, id FROM peers;",
|
||||
[this](const QVector<QVariant>& row) {
|
||||
peers[row[0].toString()] = row[1].toInt();
|
||||
}});
|
||||
}
|
||||
|
||||
History::~History()
|
||||
{
|
||||
if (!isValid())
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -88,16 +88,15 @@ bool History::isValid()
|
||||
*/
|
||||
void History::eraseHistory()
|
||||
{
|
||||
if (!isValid())
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
db->execNow("DELETE FROM faux_offline_pending;"
|
||||
"DELETE FROM history;"
|
||||
"DELETE FROM aliases;"
|
||||
"DELETE FROM peers;"
|
||||
"VACUUM;");
|
||||
"DELETE FROM history;"
|
||||
"DELETE FROM aliases;"
|
||||
"DELETE FROM peers;"
|
||||
"VACUUM;");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,36 +105,31 @@ void History::eraseHistory()
|
||||
*/
|
||||
void History::removeFriendHistory(const QString& friendPk)
|
||||
{
|
||||
if (!isValid())
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!peers.contains(friendPk))
|
||||
{
|
||||
if (!peers.contains(friendPk)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t id = peers[friendPk];
|
||||
|
||||
QString queryText = QString(
|
||||
"DELETE FROM faux_offline_pending "
|
||||
"WHERE faux_offline_pending.id IN ( "
|
||||
" SELECT faux_offline_pending.id FROM faux_offline_pending "
|
||||
" LEFT JOIN history ON faux_offline_pending.id = history.id "
|
||||
" WHERE chat_id=%1 "
|
||||
"); "
|
||||
"DELETE FROM history WHERE chat_id=%1; "
|
||||
"DELETE FROM aliases WHERE owner=%1; "
|
||||
"DELETE FROM peers WHERE id=%1; "
|
||||
"VACUUM;").arg(id);
|
||||
QString queryText = QString("DELETE FROM faux_offline_pending "
|
||||
"WHERE faux_offline_pending.id IN ( "
|
||||
" SELECT faux_offline_pending.id FROM faux_offline_pending "
|
||||
" LEFT JOIN history ON faux_offline_pending.id = history.id "
|
||||
" WHERE chat_id=%1 "
|
||||
"); "
|
||||
"DELETE FROM history WHERE chat_id=%1; "
|
||||
"DELETE FROM aliases WHERE owner=%1; "
|
||||
"DELETE FROM peers WHERE id=%1; "
|
||||
"VACUUM;")
|
||||
.arg(id);
|
||||
|
||||
if (db->execNow(queryText))
|
||||
{
|
||||
if (db->execNow(queryText)) {
|
||||
peers.remove(friendPk);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "Failed to remove friend's history";
|
||||
}
|
||||
}
|
||||
@ -150,78 +144,73 @@ void History::removeFriendHistory(const QString& friendPk)
|
||||
* @param dispName Name, which should be displayed.
|
||||
* @param insertIdCallback Function, called after query execution.
|
||||
*/
|
||||
QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString& friendPk, const QString& message,
|
||||
const QString& sender, const QDateTime& time, bool isSent, QString dispName,
|
||||
std::function<void(int64_t)> insertIdCallback)
|
||||
QVector<RawDatabase::Query>
|
||||
History::generateNewMessageQueries(const QString& friendPk, const QString& message,
|
||||
const QString& sender, const QDateTime& time, bool isSent,
|
||||
QString dispName, std::function<void(int64_t)> insertIdCallback)
|
||||
{
|
||||
QVector<RawDatabase::Query> queries;
|
||||
|
||||
// Get the db id of the peer we're chatting with
|
||||
int64_t peerId;
|
||||
if (peers.contains(friendPk))
|
||||
{
|
||||
if (peers.contains(friendPk)) {
|
||||
peerId = peers[friendPk];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (peers.isEmpty())
|
||||
{
|
||||
} else {
|
||||
if (peers.isEmpty()) {
|
||||
peerId = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
peerId = *std::max_element(peers.begin(), peers.end()) + 1;
|
||||
}
|
||||
|
||||
peers[friendPk] = peerId;
|
||||
queries += RawDatabase::Query(("INSERT INTO peers (id, public_key) "
|
||||
"VALUES (%1, '" + friendPk + "');")
|
||||
.arg(peerId));
|
||||
"VALUES (%1, '"
|
||||
+ friendPk + "');")
|
||||
.arg(peerId));
|
||||
}
|
||||
|
||||
// Get the db id of the sender of the message
|
||||
int64_t senderId;
|
||||
if (peers.contains(sender))
|
||||
{
|
||||
if (peers.contains(sender)) {
|
||||
senderId = peers[sender];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (peers.isEmpty())
|
||||
{
|
||||
} else {
|
||||
if (peers.isEmpty()) {
|
||||
senderId = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
senderId = *std::max_element(peers.begin(), peers.end()) + 1;
|
||||
}
|
||||
|
||||
peers[sender] = senderId;
|
||||
queries += RawDatabase::Query{("INSERT INTO peers (id, public_key) "
|
||||
"VALUES (%1, '" + sender + "');")
|
||||
.arg(senderId)};
|
||||
"VALUES (%1, '"
|
||||
+ sender + "');")
|
||||
.arg(senderId)};
|
||||
}
|
||||
|
||||
queries += RawDatabase::Query(QString("INSERT OR IGNORE INTO aliases (owner, display_name) VALUES (%1, ?);")
|
||||
.arg(senderId), {dispName.toUtf8()});
|
||||
queries += RawDatabase::Query(
|
||||
QString("INSERT OR IGNORE INTO aliases (owner, display_name) VALUES (%1, ?);").arg(senderId),
|
||||
{dispName.toUtf8()});
|
||||
|
||||
// If the alias already existed, the insert will ignore the conflict and last_insert_rowid() will return garbage,
|
||||
// If the alias already existed, the insert will ignore the conflict and last_insert_rowid()
|
||||
// will return garbage,
|
||||
// so we have to check changes() and manually fetch the row ID in this case
|
||||
queries += RawDatabase::Query(QString("INSERT INTO history (timestamp, chat_id, message, sender_alias) "
|
||||
"VALUES (%1, %2, ?, ("
|
||||
" CASE WHEN changes() IS 0 THEN ("
|
||||
" SELECT id FROM aliases WHERE owner=%3 AND display_name=?)"
|
||||
" ELSE last_insert_rowid() END"
|
||||
"));")
|
||||
.arg(time.toMSecsSinceEpoch()).arg(peerId).arg(senderId),
|
||||
{message.toUtf8(), dispName.toUtf8()}, insertIdCallback);
|
||||
queries +=
|
||||
RawDatabase::Query(QString(
|
||||
"INSERT INTO history (timestamp, chat_id, message, sender_alias) "
|
||||
"VALUES (%1, %2, ?, ("
|
||||
" CASE WHEN changes() IS 0 THEN ("
|
||||
" SELECT id FROM aliases WHERE owner=%3 AND display_name=?)"
|
||||
" ELSE last_insert_rowid() END"
|
||||
"));")
|
||||
.arg(time.toMSecsSinceEpoch())
|
||||
.arg(peerId)
|
||||
.arg(senderId),
|
||||
{message.toUtf8(), dispName.toUtf8()}, insertIdCallback);
|
||||
|
||||
if (!isSent)
|
||||
{
|
||||
queries += RawDatabase::Query{
|
||||
"INSERT INTO faux_offline_pending (id) VALUES ("
|
||||
" last_insert_rowid()"
|
||||
");"};
|
||||
if (!isSent) {
|
||||
queries += RawDatabase::Query{"INSERT INTO faux_offline_pending (id) VALUES ("
|
||||
" last_insert_rowid()"
|
||||
");"};
|
||||
}
|
||||
|
||||
return queries;
|
||||
@ -237,18 +226,16 @@ QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString& fr
|
||||
* @param dispName Name, which should be displayed.
|
||||
* @param insertIdCallback Function, called after query execution.
|
||||
*/
|
||||
void History::addNewMessage(const QString& friendPk, const QString& message,
|
||||
const QString& sender, const QDateTime& time,
|
||||
bool isSent, QString dispName,
|
||||
void History::addNewMessage(const QString& friendPk, const QString& message, const QString& sender,
|
||||
const QDateTime& time, bool isSent, QString dispName,
|
||||
std::function<void(int64_t)> insertIdCallback)
|
||||
{
|
||||
if (!isValid())
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
db->execLater(generateNewMessageQueries(friendPk, message, sender, time,
|
||||
isSent, dispName, insertIdCallback));
|
||||
db->execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName,
|
||||
insertIdCallback));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -258,33 +245,30 @@ void History::addNewMessage(const QString& friendPk, const QString& message,
|
||||
* @param to End of period to fetch.
|
||||
* @return List of messages.
|
||||
*/
|
||||
QList<History::HistMessage> History::getChatHistory(const QString& friendPk,
|
||||
const QDateTime& from,
|
||||
QList<History::HistMessage> History::getChatHistory(const QString& friendPk, const QDateTime& from,
|
||||
const QDateTime& to)
|
||||
{
|
||||
if (!isValid())
|
||||
{
|
||||
if (!isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<HistMessage> messages;
|
||||
|
||||
auto rowCallback = [&messages](const QVector<QVariant>& row)
|
||||
{
|
||||
auto rowCallback = [&messages](const QVector<QVariant>& row) {
|
||||
// dispName and message could have null bytes, QString::fromUtf8
|
||||
// truncates on null bytes so we strip them
|
||||
messages += {row[0].toLongLong(),
|
||||
row[1].isNull(),
|
||||
QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()),
|
||||
row[3].toString(),
|
||||
QString::fromUtf8(row[4].toByteArray().replace('\0',"")),
|
||||
row[5].toString(),
|
||||
QString::fromUtf8(row[6].toByteArray().replace('\0',""))};
|
||||
row[1].isNull(),
|
||||
QDateTime::fromMSecsSinceEpoch(row[2].toLongLong()),
|
||||
row[3].toString(),
|
||||
QString::fromUtf8(row[4].toByteArray().replace('\0', "")),
|
||||
row[5].toString(),
|
||||
QString::fromUtf8(row[6].toByteArray().replace('\0', ""))};
|
||||
};
|
||||
|
||||
// Don't forget to update the rowCallback if you change the selected columns!
|
||||
QString queryText = QString(
|
||||
"SELECT history.id, faux_offline_pending.id, timestamp, "
|
||||
QString queryText =
|
||||
QString("SELECT history.id, faux_offline_pending.id, timestamp, "
|
||||
"chat.public_key, aliases.display_name, sender.public_key, "
|
||||
"message FROM history "
|
||||
"LEFT JOIN faux_offline_pending ON history.id = faux_offline_pending.id "
|
||||
@ -292,7 +276,9 @@ QList<History::HistMessage> History::getChatHistory(const QString& friendPk,
|
||||
"JOIN aliases ON sender_alias = aliases.id "
|
||||
"JOIN peers sender ON aliases.owner = sender.id "
|
||||
"WHERE timestamp BETWEEN %1 AND %2 AND chat.public_key='%3';")
|
||||
.arg(from.toMSecsSinceEpoch()).arg(to.toMSecsSinceEpoch()).arg(friendPk);
|
||||
.arg(from.toMSecsSinceEpoch())
|
||||
.arg(to.toMSecsSinceEpoch())
|
||||
.arg(friendPk);
|
||||
|
||||
db->execNow({queryText, rowCallback});
|
||||
|
||||
@ -307,11 +293,9 @@ QList<History::HistMessage> History::getChatHistory(const QString& friendPk,
|
||||
*/
|
||||
void History::markAsSent(qint64 messageId)
|
||||
{
|
||||
if (!isValid())
|
||||
{
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
db->execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;")
|
||||
.arg(messageId));
|
||||
db->execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(messageId));
|
||||
}
|
||||
|
@ -21,8 +21,8 @@
|
||||
#define HISTORY_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QVector>
|
||||
#include <QHash>
|
||||
#include <QVector>
|
||||
|
||||
#include <cstdint>
|
||||
#include <tox/toxencryptsave.h>
|
||||
@ -33,14 +33,19 @@ class Profile;
|
||||
class HistoryKeeper;
|
||||
|
||||
class History
|
||||
{
|
||||
{
|
||||
public:
|
||||
struct HistMessage
|
||||
{
|
||||
HistMessage(qint64 id, bool isSent, QDateTime timestamp, QString chat,
|
||||
QString dispName, QString sender, QString message)
|
||||
: chat{chat}, sender{sender}, message{message}, dispName{dispName}
|
||||
, timestamp{timestamp}, id{id}, isSent{isSent}
|
||||
HistMessage(qint64 id, bool isSent, QDateTime timestamp, QString chat, QString dispName,
|
||||
QString sender, QString message)
|
||||
: chat{chat}
|
||||
, sender{sender}
|
||||
, message{message}
|
||||
, dispName{dispName}
|
||||
, timestamp{timestamp}
|
||||
, id{id}
|
||||
, isSent{isSent}
|
||||
{
|
||||
}
|
||||
|
||||
@ -62,21 +67,19 @@ public:
|
||||
|
||||
void eraseHistory();
|
||||
void removeFriendHistory(const QString& friendPk);
|
||||
void addNewMessage(const QString& friendPk, const QString& message,
|
||||
const QString& sender, const QDateTime& time,
|
||||
bool isSent, QString dispName,
|
||||
std::function<void(int64_t)> insertIdCallback={});
|
||||
void addNewMessage(const QString& friendPk, const QString& message, const QString& sender,
|
||||
const QDateTime& time, bool isSent, QString dispName,
|
||||
std::function<void(int64_t)> insertIdCallback = {});
|
||||
|
||||
QList<HistMessage> getChatHistory(const QString& friendPk,
|
||||
const QDateTime& from,
|
||||
QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime& from,
|
||||
const QDateTime& to);
|
||||
void markAsSent(qint64 messageId);
|
||||
|
||||
protected:
|
||||
QVector<RawDatabase::Query> generateNewMessageQueries(
|
||||
const QString& friendPk, const QString& message,
|
||||
const QString& sender, const QDateTime& time,
|
||||
bool isSent, QString dispName,
|
||||
std::function<void(int64_t)> insertIdCallback={});
|
||||
QVector<RawDatabase::Query>
|
||||
generateNewMessageQueries(const QString& friendPk, const QString& message,
|
||||
const QString& sender, const QDateTime& time, bool isSent,
|
||||
QString dispName, std::function<void(int64_t)> insertIdCallback = {});
|
||||
|
||||
private:
|
||||
std::shared_ptr<RawDatabase> db;
|
||||
|
@ -18,11 +18,11 @@
|
||||
*/
|
||||
|
||||
#include "offlinemsgengine.h"
|
||||
#include "src/friend.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/friend.h"
|
||||
#include "src/nexus.h"
|
||||
#include "src/persistence/profile.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include <QMutexLocker>
|
||||
#include <QTimer>
|
||||
|
||||
@ -38,9 +38,9 @@
|
||||
const int OfflineMsgEngine::offlineTimeout = 20000;
|
||||
QMutex OfflineMsgEngine::globalMutex;
|
||||
|
||||
OfflineMsgEngine::OfflineMsgEngine(Friend* frnd) :
|
||||
mutex(QMutex::Recursive),
|
||||
f(frnd)
|
||||
OfflineMsgEngine::OfflineMsgEngine(Friend* frnd)
|
||||
: mutex(QMutex::Recursive)
|
||||
, f(frnd)
|
||||
{
|
||||
}
|
||||
|
||||
@ -54,12 +54,10 @@ void OfflineMsgEngine::dischargeReceipt(int receipt)
|
||||
|
||||
Profile* profile = Nexus::getProfile();
|
||||
auto it = receipts.find(receipt);
|
||||
if (it != receipts.end())
|
||||
{
|
||||
if (it != receipts.end()) {
|
||||
int mID = it.value();
|
||||
auto msgIt = undeliveredMsgs.find(mID);
|
||||
if (msgIt != undeliveredMsgs.end())
|
||||
{
|
||||
if (msgIt != undeliveredMsgs.end()) {
|
||||
if (profile->isHistoryEnabled())
|
||||
profile->getHistory()->markAsSent(mID);
|
||||
msgIt.value().msg->markAsSent(QDateTime::currentDateTime());
|
||||
@ -69,7 +67,8 @@ void OfflineMsgEngine::dischargeReceipt(int receipt)
|
||||
}
|
||||
}
|
||||
|
||||
void OfflineMsgEngine::registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg, const QDateTime ×tamp)
|
||||
void OfflineMsgEngine::registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg,
|
||||
const QDateTime& timestamp)
|
||||
{
|
||||
QMutexLocker ml(&mutex);
|
||||
|
||||
@ -94,24 +93,19 @@ void OfflineMsgEngine::deliverOfflineMsgs()
|
||||
removeAllReceipts();
|
||||
undeliveredMsgs.clear();
|
||||
|
||||
for (auto iter = msgs.begin(); iter != msgs.end(); ++iter)
|
||||
{
|
||||
for (auto iter = msgs.begin(); iter != msgs.end(); ++iter) {
|
||||
auto val = iter.value();
|
||||
auto key = iter.key();
|
||||
|
||||
if (val.timestamp.msecsTo(QDateTime::currentDateTime()) < offlineTimeout)
|
||||
{
|
||||
if (val.timestamp.msecsTo(QDateTime::currentDateTime()) < offlineTimeout) {
|
||||
registerReceipt(val.receipt, key, val.msg, val.timestamp);
|
||||
continue;
|
||||
}
|
||||
QString messageText = val.msg->toString();
|
||||
int rec;
|
||||
if (val.msg->isAction())
|
||||
{
|
||||
if (val.msg->isAction()) {
|
||||
rec = Core::getInstance()->sendAction(f->getFriendId(), messageText);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
rec = Core::getInstance()->sendMessage(f->getFriendId(), messageText);
|
||||
}
|
||||
|
||||
|
@ -20,12 +20,12 @@
|
||||
#ifndef OFFLINEMSGENGINE_H
|
||||
#define OFFLINEMSGENGINE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QMutex>
|
||||
#include "src/chatlog/chatmessage.h"
|
||||
#include <QDateTime>
|
||||
#include <QMap>
|
||||
#include "src/chatlog/chatmessage.h"
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
class Friend;
|
||||
class QTimer;
|
||||
@ -34,19 +34,21 @@ class OfflineMsgEngine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OfflineMsgEngine(Friend *);
|
||||
explicit OfflineMsgEngine(Friend*);
|
||||
virtual ~OfflineMsgEngine();
|
||||
static QMutex globalMutex;
|
||||
|
||||
void dischargeReceipt(int receipt);
|
||||
void registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg, const QDateTime ×tamp = QDateTime::currentDateTime());
|
||||
void registerReceipt(int receipt, int64_t messageID, ChatMessage::Ptr msg,
|
||||
const QDateTime& timestamp = QDateTime::currentDateTime());
|
||||
|
||||
public slots:
|
||||
void deliverOfflineMsgs();
|
||||
void removeAllReceipts();
|
||||
|
||||
private:
|
||||
struct MsgPtr {
|
||||
struct MsgPtr
|
||||
{
|
||||
ChatMessage::Ptr msg;
|
||||
QDateTime timestamp;
|
||||
int receipt;
|
||||
|
@ -50,8 +50,10 @@
|
||||
QVector<QString> Profile::profiles;
|
||||
|
||||
Profile::Profile(QString name, const QString& password, bool isNewProfile)
|
||||
: name{name}, password{password}
|
||||
, newProfile{isNewProfile}, isRemoved{false}
|
||||
: name{name}
|
||||
, password{password}
|
||||
, newProfile{isNewProfile}
|
||||
, isRemoved{false}
|
||||
{
|
||||
Settings& s = Settings::getInstance();
|
||||
s.setCurrentProfile(name);
|
||||
@ -75,15 +77,14 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile)
|
||||
*/
|
||||
Profile* Profile::loadProfile(QString name, const QString& password)
|
||||
{
|
||||
if (ProfileLocker::hasLock())
|
||||
{
|
||||
qCritical() << "Tried to load profile "<<name<<", but another profile is already locked!";
|
||||
if (ProfileLocker::hasLock()) {
|
||||
qCritical() << "Tried to load profile " << name
|
||||
<< ", but another profile is already locked!";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ProfileLocker::lock(name))
|
||||
{
|
||||
qWarning() << "Failed to lock profile "<<name;
|
||||
if (!ProfileLocker::lock(name)) {
|
||||
qWarning() << "Failed to lock profile " << name;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -93,60 +94,50 @@ Profile* Profile::loadProfile(QString name, const QString& password)
|
||||
{
|
||||
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox";
|
||||
QFile saveFile(path);
|
||||
qDebug() << "Loading tox save "<<path;
|
||||
qDebug() << "Loading tox save " << path;
|
||||
|
||||
if (!saveFile.exists())
|
||||
{
|
||||
qWarning() << "The tox save file "<<path<<" was not found";
|
||||
if (!saveFile.exists()) {
|
||||
qWarning() << "The tox save file " << path << " was not found";
|
||||
ProfileLocker::unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!saveFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!saveFile.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "The tox save file " << path << " couldn't' be opened";
|
||||
ProfileLocker::unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qint64 fileSize = saveFile.size();
|
||||
if (fileSize <= 0)
|
||||
{
|
||||
qWarning() << "The tox save file"<<path<<" is empty!";
|
||||
if (fileSize <= 0) {
|
||||
qWarning() << "The tox save file" << path << " is empty!";
|
||||
ProfileLocker::unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QByteArray data = saveFile.readAll();
|
||||
if (ToxEncrypt::isEncrypted(data))
|
||||
{
|
||||
if (password.isEmpty())
|
||||
{
|
||||
if (ToxEncrypt::isEncrypted(data)) {
|
||||
if (password.isEmpty()) {
|
||||
qCritical() << "The tox save file is encrypted, but we don't have a password!";
|
||||
ProfileLocker::unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tmpKey = ToxEncrypt::makeToxEncrypt(password, data);
|
||||
if (!tmpKey)
|
||||
{
|
||||
if (!tmpKey) {
|
||||
qCritical() << "Failed to derive key of the tox save file";
|
||||
ProfileLocker::unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
data = tmpKey->decrypt(data);
|
||||
if (data.isEmpty())
|
||||
{
|
||||
if (data.isEmpty()) {
|
||||
qCritical() << "Failed to decrypt the tox save file";
|
||||
ProfileLocker::unlock();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!password.isEmpty())
|
||||
{
|
||||
} else {
|
||||
if (!password.isEmpty()) {
|
||||
qWarning() << "We have a password, but the tox save file is not encrypted";
|
||||
}
|
||||
}
|
||||
@ -169,31 +160,27 @@ Profile* Profile::loadProfile(QString name, const QString& password)
|
||||
Profile* Profile::createProfile(QString name, QString password)
|
||||
{
|
||||
std::unique_ptr<ToxEncrypt> tmpKey;
|
||||
if(!password.isEmpty())
|
||||
{
|
||||
if (!password.isEmpty()) {
|
||||
tmpKey = ToxEncrypt::makeToxEncrypt(password);
|
||||
if (!tmpKey)
|
||||
{
|
||||
if (!tmpKey) {
|
||||
qCritical() << "Failed to derive key for the tox save";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (ProfileLocker::hasLock())
|
||||
{
|
||||
qCritical() << "Tried to create profile "<<name<<", but another profile is already locked!";
|
||||
if (ProfileLocker::hasLock()) {
|
||||
qCritical() << "Tried to create profile " << name
|
||||
<< ", but another profile is already locked!";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (exists(name))
|
||||
{
|
||||
qCritical() << "Tried to create profile "<<name<<", but it already exists!";
|
||||
if (exists(name)) {
|
||||
qCritical() << "Tried to create profile " << name << ", but it already exists!";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ProfileLocker::lock(name))
|
||||
{
|
||||
qWarning() << "Failed to lock profile "<<name;
|
||||
if (!ProfileLocker::lock(name)) {
|
||||
qWarning() << "Failed to lock profile " << name;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -206,8 +193,7 @@ Profile* Profile::createProfile(QString name, QString password)
|
||||
|
||||
Profile::~Profile()
|
||||
{
|
||||
if (!isRemoved && core->isReady())
|
||||
{
|
||||
if (!isRemoved && core->isReady()) {
|
||||
saveToxSave();
|
||||
}
|
||||
|
||||
@ -216,8 +202,7 @@ Profile::~Profile()
|
||||
qApp->processEvents();
|
||||
|
||||
delete coreThread;
|
||||
if (!isRemoved)
|
||||
{
|
||||
if (!isRemoved) {
|
||||
Settings::getInstance().savePersonal(this);
|
||||
Settings::getInstance().sync();
|
||||
ProfileLocker::assertLock();
|
||||
@ -236,11 +221,10 @@ QVector<QString> Profile::getFilesByExt(QString extension)
|
||||
QDir dir(Settings::getInstance().getSettingsDirPath());
|
||||
QVector<QString> out;
|
||||
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
|
||||
dir.setNameFilters(QStringList("*."+extension));
|
||||
dir.setNameFilters(QStringList("*." + extension));
|
||||
QFileInfoList list = dir.entryInfoList();
|
||||
out.reserve(list.size());
|
||||
for (QFileInfo file : list)
|
||||
{
|
||||
for (QFileInfo file : list) {
|
||||
out += file.completeBaseName();
|
||||
}
|
||||
|
||||
@ -255,10 +239,8 @@ void Profile::scanProfiles()
|
||||
{
|
||||
profiles.clear();
|
||||
QVector<QString> toxfiles = getFilesByExt("tox"), inifiles = getFilesByExt("ini");
|
||||
for (QString toxfile : toxfiles)
|
||||
{
|
||||
if (!inifiles.contains(toxfile))
|
||||
{
|
||||
for (QString toxfile : toxfiles) {
|
||||
if (!inifiles.contains(toxfile)) {
|
||||
Settings::getInstance().createPersonal(toxfile);
|
||||
}
|
||||
|
||||
@ -306,49 +288,40 @@ QByteArray Profile::loadToxSave()
|
||||
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox";
|
||||
QFile saveFile(path);
|
||||
qint64 fileSize;
|
||||
qDebug() << "Loading tox save "<<path;
|
||||
qDebug() << "Loading tox save " << path;
|
||||
|
||||
if (!saveFile.exists())
|
||||
{
|
||||
qWarning() << "The tox save file "<<path<<" was not found";
|
||||
if (!saveFile.exists()) {
|
||||
qWarning() << "The tox save file " << path << " was not found";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!saveFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!saveFile.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "The tox save file " << path << " couldn't' be opened";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fileSize = saveFile.size();
|
||||
if (fileSize <= 0)
|
||||
{
|
||||
qWarning() << "The tox save file"<<path<<" is empty!";
|
||||
if (fileSize <= 0) {
|
||||
qWarning() << "The tox save file" << path << " is empty!";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data = saveFile.readAll();
|
||||
if (ToxEncrypt::isEncrypted(data))
|
||||
{
|
||||
if (password.isEmpty())
|
||||
{
|
||||
if (ToxEncrypt::isEncrypted(data)) {
|
||||
if (password.isEmpty()) {
|
||||
qCritical() << "The tox save file is encrypted, but we don't have a password!";
|
||||
data.clear();
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data = passkey->decrypt(data);
|
||||
if (data.isEmpty())
|
||||
{
|
||||
if (data.isEmpty()) {
|
||||
qCritical() << "Failed to decrypt the tox save file";
|
||||
data.clear();
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!password.isEmpty())
|
||||
{
|
||||
} else {
|
||||
if (!password.isEmpty()) {
|
||||
qWarning() << "We have a password, but the tox save file is not encrypted";
|
||||
}
|
||||
}
|
||||
@ -382,19 +355,16 @@ void Profile::saveToxSave(QByteArray data)
|
||||
assert(ProfileLocker::getCurLockName() == name);
|
||||
|
||||
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox";
|
||||
qDebug() << "Saving tox save to "<<path;
|
||||
qDebug() << "Saving tox save to " << path;
|
||||
QSaveFile saveFile(path);
|
||||
if (!saveFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!saveFile.open(QIODevice::WriteOnly)) {
|
||||
qCritical() << "Tox save file " << path << " couldn't be opened";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password.isEmpty())
|
||||
{
|
||||
if (!password.isEmpty()) {
|
||||
data = passkey->encrypt(data);
|
||||
if (data.isEmpty())
|
||||
{
|
||||
if (data.isEmpty()) {
|
||||
qCritical() << "Failed to encrypt, can't save!";
|
||||
saveFile.cancelWriting();
|
||||
return;
|
||||
@ -404,22 +374,21 @@ void Profile::saveToxSave(QByteArray data)
|
||||
saveFile.write(data);
|
||||
|
||||
// check if everything got written
|
||||
if (saveFile.flush())
|
||||
{
|
||||
if (saveFile.flush()) {
|
||||
saveFile.commit();
|
||||
newProfile = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
saveFile.cancelWriting();
|
||||
qCritical() << "Failed to write, can't save!";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the path of the avatar file cached by this profile and corresponding to this owner ID.
|
||||
* @brief Gets the path of the avatar file cached by this profile and corresponding to this owner
|
||||
* ID.
|
||||
* @param ownerId Path to avatar of friend with this ID will returned.
|
||||
* @param forceUnencrypted If true, return the path to the plaintext file even if this is an encrypted profile.
|
||||
* @param forceUnencrypted If true, return the path to the plaintext file even if this is an
|
||||
* encrypted profile.
|
||||
* @return Path to the avatar.
|
||||
*/
|
||||
QString Profile::avatarPath(const QString& ownerId, bool forceUnencrypted)
|
||||
@ -430,13 +399,16 @@ QString Profile::avatarPath(const QString& ownerId, bool forceUnencrypted)
|
||||
QByteArray idData = ownerId.toUtf8();
|
||||
QByteArray pubkeyData = core->getSelfId().getPublicKey().getKey();
|
||||
constexpr int hashSize = TOX_PUBLIC_KEY_SIZE;
|
||||
static_assert(hashSize >= crypto_generichash_BYTES_MIN
|
||||
&& hashSize <= crypto_generichash_BYTES_MAX, "Hash size not supported by libsodium");
|
||||
static_assert(hashSize >= crypto_generichash_BYTES_MIN && hashSize <= crypto_generichash_BYTES_MAX,
|
||||
"Hash size not supported by libsodium");
|
||||
static_assert(hashSize >= crypto_generichash_KEYBYTES_MIN
|
||||
&& hashSize <= crypto_generichash_KEYBYTES_MAX, "Key size not supported by libsodium");
|
||||
&& hashSize <= crypto_generichash_KEYBYTES_MAX,
|
||||
"Key size not supported by libsodium");
|
||||
QByteArray hash(hashSize, 0);
|
||||
crypto_generichash((uint8_t*)hash.data(), hashSize, (uint8_t*)idData.data(), idData.size(), (uint8_t*)pubkeyData.data(), pubkeyData.size());
|
||||
return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() + ".png";
|
||||
crypto_generichash((uint8_t*)hash.data(), hashSize, (uint8_t*)idData.data(), idData.size(),
|
||||
(uint8_t*)pubkeyData.data(), pubkeyData.size());
|
||||
return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper()
|
||||
+ ".png";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -467,7 +439,7 @@ QPixmap Profile::loadAvatar(const QString& ownerId)
|
||||
*/
|
||||
QByteArray Profile::loadAvatarData(const QString& ownerId)
|
||||
{
|
||||
return loadAvatarData(ownerId, password);
|
||||
return loadAvatarData(ownerId, password);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -482,21 +454,18 @@ QByteArray Profile::loadAvatarData(const QString& ownerId, const QString& passwo
|
||||
bool encrypted = !password.isEmpty();
|
||||
|
||||
// If the encrypted avatar isn't found, try loading the unencrypted one for the same ID
|
||||
if (!password.isEmpty() && !QFile::exists(path))
|
||||
{
|
||||
if (!password.isEmpty() && !QFile::exists(path)) {
|
||||
encrypted = false;
|
||||
path = avatarPath(ownerId, true);
|
||||
}
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray pic = file.readAll();
|
||||
if (encrypted && !pic.isEmpty())
|
||||
{
|
||||
if (encrypted && !pic.isEmpty()) {
|
||||
// TODO: check if we can use passkey-decrypt(pic) here
|
||||
pic = ToxEncrypt::decryptPass(password, pic);
|
||||
}
|
||||
@ -506,29 +475,27 @@ QByteArray Profile::loadAvatarData(const QString& ownerId, const QString& passwo
|
||||
|
||||
void Profile::loadDatabase(const ToxId& id)
|
||||
{
|
||||
if(isRemoved)
|
||||
{
|
||||
if (isRemoved) {
|
||||
qDebug() << "Can't load database of removed profile";
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray salt = id.getPublicKey().getKey();
|
||||
if(salt.size() != TOX_PASS_SALT_LENGTH)
|
||||
{
|
||||
if (salt.size() != TOX_PASS_SALT_LENGTH) {
|
||||
qWarning() << "Couldn't compute salt from public key" << name;
|
||||
GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
|
||||
GUI::showError(QObject::tr("Error"),
|
||||
QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
|
||||
}
|
||||
// At this point it's too early to load the personal settings (Nexus will do it), so we always load
|
||||
// At this point it's too early to load the personal settings (Nexus will do it), so we always
|
||||
// load
|
||||
// the history, and if it fails we can't change the setting now, but we keep a nullptr
|
||||
database = std::make_shared<RawDatabase>(getDbPath(name), password, salt);
|
||||
if (database && database->isOpen())
|
||||
{
|
||||
if (database && database->isOpen()) {
|
||||
history.reset(new History(database));
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "Failed to open database for profile" << name;
|
||||
GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
|
||||
GUI::showError(QObject::tr("Error"),
|
||||
QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,22 +506,17 @@ void Profile::loadDatabase(const ToxId& id)
|
||||
*/
|
||||
void Profile::saveAvatar(QByteArray pic, const QString& ownerId)
|
||||
{
|
||||
if (!password.isEmpty() && !pic.isEmpty())
|
||||
{
|
||||
if (!password.isEmpty() && !pic.isEmpty()) {
|
||||
pic = passkey->encrypt(pic);
|
||||
}
|
||||
|
||||
QString path = avatarPath(ownerId);
|
||||
QDir(Settings::getInstance().getSettingsDirPath()).mkdir("avatars");
|
||||
if (pic.isEmpty())
|
||||
{
|
||||
if (pic.isEmpty()) {
|
||||
QFile::remove(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
QSaveFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qWarning() << "Tox avatar " << path << " couldn't be saved";
|
||||
return;
|
||||
}
|
||||
@ -585,7 +547,8 @@ void Profile::removeAvatar()
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks that the history is enabled in the settings, and loaded successfully for this profile.
|
||||
* @brief Checks that the history is enabled in the settings, and loaded successfully for this
|
||||
* profile.
|
||||
* @return True if enabled, false otherwise.
|
||||
*/
|
||||
bool Profile::isHistoryEnabled()
|
||||
@ -616,7 +579,7 @@ void Profile::removeAvatar(const QString& ownerId)
|
||||
bool Profile::exists(QString name)
|
||||
{
|
||||
QString path = Settings::getInstance().getSettingsDirPath() + name;
|
||||
return QFile::exists(path+".tox");
|
||||
return QFile::exists(path + ".tox");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -639,9 +602,8 @@ bool Profile::isEncrypted(QString name)
|
||||
uint8_t data[TOX_PASS_ENCRYPTION_EXTRA_LENGTH] = {0};
|
||||
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox";
|
||||
QFile saveFile(path);
|
||||
if (!saveFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qWarning() << "Couldn't open tox save "<<path;
|
||||
if (!saveFile.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Couldn't open tox save " << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -659,18 +621,15 @@ bool Profile::isEncrypted(QString name)
|
||||
*/
|
||||
QVector<QString> Profile::remove()
|
||||
{
|
||||
if (isRemoved)
|
||||
{
|
||||
if (isRemoved) {
|
||||
qWarning() << "Profile " << name << " is already removed!";
|
||||
return {};
|
||||
}
|
||||
isRemoved = true;
|
||||
|
||||
qDebug() << "Removing profile" << name;
|
||||
for (int i=0; i<profiles.size(); ++i)
|
||||
{
|
||||
if (profiles[i] == name)
|
||||
{
|
||||
for (int i = 0; i < profiles.size(); ++i) {
|
||||
if (profiles[i] == name) {
|
||||
profiles.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
@ -678,25 +637,22 @@ QVector<QString> Profile::remove()
|
||||
QString path = Settings::getInstance().getSettingsDirPath() + name;
|
||||
ProfileLocker::unlock();
|
||||
|
||||
QFile profileMain {path + ".tox"};
|
||||
QFile profileConfig {path + ".ini"};
|
||||
QFile profileMain{path + ".tox"};
|
||||
QFile profileConfig{path + ".ini"};
|
||||
|
||||
QVector<QString> ret;
|
||||
|
||||
if (!profileMain.remove() && profileMain.exists())
|
||||
{
|
||||
if (!profileMain.remove() && profileMain.exists()) {
|
||||
ret.push_back(profileMain.fileName());
|
||||
qWarning() << "Could not remove file " << profileMain.fileName();
|
||||
}
|
||||
if (!profileConfig.remove() && profileConfig.exists())
|
||||
{
|
||||
if (!profileConfig.remove() && profileConfig.exists()) {
|
||||
ret.push_back(profileConfig.fileName());
|
||||
qWarning() << "Could not remove file " << profileConfig.fileName();
|
||||
}
|
||||
|
||||
QString dbPath = getDbPath(name);
|
||||
if (database && database->isOpen() && !database->remove() && QFile::exists(dbPath))
|
||||
{
|
||||
if (database && database->isOpen() && !database->remove() && QFile::exists(dbPath)) {
|
||||
ret.push_back(dbPath);
|
||||
qWarning() << "Could not remove file " << dbPath;
|
||||
}
|
||||
@ -717,23 +673,20 @@ bool Profile::rename(QString newName)
|
||||
QString path = Settings::getInstance().getSettingsDirPath() + name,
|
||||
newPath = Settings::getInstance().getSettingsDirPath() + newName;
|
||||
|
||||
if (!ProfileLocker::lock(newName))
|
||||
{
|
||||
if (!ProfileLocker::lock(newName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile::rename(path + ".tox", newPath + ".tox");
|
||||
QFile::rename(path + ".ini", newPath + ".ini");
|
||||
if (database)
|
||||
{
|
||||
if (database) {
|
||||
database->rename(newName);
|
||||
}
|
||||
|
||||
bool resetAutorun = Settings::getInstance().getAutorun();
|
||||
Settings::getInstance().setAutorun(false);
|
||||
Settings::getInstance().setCurrentProfile(newName);
|
||||
if (resetAutorun)
|
||||
{
|
||||
if (resetAutorun) {
|
||||
Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line
|
||||
}
|
||||
|
||||
@ -747,8 +700,7 @@ bool Profile::rename(QString newName)
|
||||
*/
|
||||
bool Profile::checkPassword()
|
||||
{
|
||||
if (isRemoved)
|
||||
{
|
||||
if (isRemoved) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -771,8 +723,7 @@ const ToxEncrypt& Profile::getPasskey() const
|
||||
void Profile::restartCore()
|
||||
{
|
||||
GUI::setEnabled(false); // Core::reset re-enables it
|
||||
if (!isRemoved && core->isReady())
|
||||
{
|
||||
if (!isRemoved && core->isReady()) {
|
||||
saveToxSave();
|
||||
}
|
||||
|
||||
@ -790,8 +741,7 @@ void Profile::setPassword(const QString& newPassword)
|
||||
std::unique_ptr<ToxEncrypt> oldpasskey = std::move(passkey);
|
||||
password = newPassword;
|
||||
passkey = ToxEncrypt::makeToxEncrypt(password);
|
||||
if(!passkey)
|
||||
{
|
||||
if (!passkey) {
|
||||
qCritical() << "Failed to derive key from password, the profile won't use the new password";
|
||||
password = oldPassword;
|
||||
passkey = std::move(oldpasskey);
|
||||
@ -800,8 +750,7 @@ void Profile::setPassword(const QString& newPassword)
|
||||
saveToxSave();
|
||||
|
||||
// TODO: ensure the database and the tox save file use the same password
|
||||
if (database)
|
||||
{
|
||||
if (database) {
|
||||
database->setPassword(newPassword);
|
||||
}
|
||||
|
||||
@ -810,8 +759,7 @@ void Profile::setPassword(const QString& newPassword)
|
||||
|
||||
QVector<uint32_t> friendList = core->getFriendList();
|
||||
QVectorIterator<uint32_t> i(friendList);
|
||||
while (i.hasNext())
|
||||
{
|
||||
while (i.hasNext()) {
|
||||
QString friendPublicKey = core->getFriendPublicKey(i.next()).toString();
|
||||
saveAvatar(loadAvatarData(friendPublicKey, oldPassword), friendPublicKey);
|
||||
}
|
||||
|
@ -21,17 +21,17 @@
|
||||
#ifndef PROFILE_H
|
||||
#define PROFILE_H
|
||||
|
||||
#include "src/core/toxid.h"
|
||||
#include "src/core/toxencrypt.h"
|
||||
#include "src/core/toxid.h"
|
||||
|
||||
#include "src/persistence/history.h"
|
||||
|
||||
#include <memory>
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <memory>
|
||||
|
||||
class Core;
|
||||
class QThread;
|
||||
@ -86,6 +86,7 @@ public:
|
||||
|
||||
private slots:
|
||||
void loadDatabase(const ToxId& id);
|
||||
|
||||
private:
|
||||
Profile(QString name, const QString& password, bool newProfile);
|
||||
static QVector<QString> getFilesByExt(QString extension);
|
||||
|
@ -20,8 +20,8 @@
|
||||
|
||||
#include "profilelocker.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
|
||||
/**
|
||||
* @class ProfileLocker
|
||||
@ -38,7 +38,7 @@ QString ProfileLocker::curLockName;
|
||||
|
||||
QString ProfileLocker::lockPathFromName(const QString& name)
|
||||
{
|
||||
return Settings::getInstance().getSettingsDirPath()+'/'+name+".lock";
|
||||
return Settings::getInstance().getSettingsDirPath() + '/' + name + ".lock";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,8 +71,7 @@ bool ProfileLocker::lock(QString profile)
|
||||
|
||||
QLockFile* newLock = new QLockFile(lockPathFromName(profile));
|
||||
newLock->setStaleLockTime(0);
|
||||
if (!newLock->tryLock())
|
||||
{
|
||||
if (!newLock->tryLock()) {
|
||||
delete newLock;
|
||||
return false;
|
||||
}
|
||||
@ -105,22 +104,17 @@ void ProfileLocker::unlock()
|
||||
*/
|
||||
void ProfileLocker::assertLock()
|
||||
{
|
||||
if (!lockfile)
|
||||
{
|
||||
if (!lockfile) {
|
||||
qCritical() << "assertLock: We don't seem to own any lock!";
|
||||
deathByBrokenLock();
|
||||
}
|
||||
|
||||
if (!QFile(lockPathFromName(curLockName)).exists())
|
||||
{
|
||||
if (!QFile(lockPathFromName(curLockName)).exists()) {
|
||||
QString tmp = curLockName;
|
||||
unlock();
|
||||
if (lock(tmp))
|
||||
{
|
||||
if (lock(tmp)) {
|
||||
qCritical() << "assertLock: Lock file was lost, but could be restored";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qCritical() << "assertLock: Lock file was lost, and could *NOT* be restored";
|
||||
deathByBrokenLock();
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
class ProfileLocker
|
||||
{
|
||||
private:
|
||||
ProfileLocker()=delete;
|
||||
ProfileLocker() = delete;
|
||||
|
||||
public:
|
||||
static bool isLockable(QString profile);
|
||||
|
@ -30,9 +30,8 @@ QString dataToString(QByteArray data)
|
||||
char num3;
|
||||
int strlen = 0;
|
||||
int num2 = 0;
|
||||
int i=0;
|
||||
do
|
||||
{
|
||||
int i = 0;
|
||||
do {
|
||||
num3 = data[i++];
|
||||
strlen |= (num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
@ -50,14 +49,10 @@ QString dataToString(QByteArray data)
|
||||
|
||||
uint64_t dataToUint64(const QByteArray& data)
|
||||
{
|
||||
return static_cast<uint64_t>(data[0])
|
||||
| (static_cast<uint64_t>(data[1]) << 8)
|
||||
| (static_cast<uint64_t>(data[2]) << 16)
|
||||
| (static_cast<uint64_t>(data[3]) << 24)
|
||||
| (static_cast<uint64_t>(data[4]) << 32)
|
||||
| (static_cast<uint64_t>(data[5]) << 40)
|
||||
| (static_cast<uint64_t>(data[6]) << 48)
|
||||
| (static_cast<uint64_t>(data[7]) << 56);
|
||||
return static_cast<uint64_t>(data[0]) | (static_cast<uint64_t>(data[1]) << 8)
|
||||
| (static_cast<uint64_t>(data[2]) << 16) | (static_cast<uint64_t>(data[3]) << 24)
|
||||
| (static_cast<uint64_t>(data[4]) << 32) | (static_cast<uint64_t>(data[5]) << 40)
|
||||
| (static_cast<uint64_t>(data[6]) << 48) | (static_cast<uint64_t>(data[7]) << 56);
|
||||
}
|
||||
|
||||
int dataToVInt(const QByteArray& data)
|
||||
@ -65,9 +60,8 @@ int dataToVInt(const QByteArray& data)
|
||||
char num3;
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
int i=0;
|
||||
do
|
||||
{
|
||||
int i = 0;
|
||||
do {
|
||||
num3 = data[i++];
|
||||
num |= static_cast<int>(num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
@ -80,9 +74,8 @@ size_t dataToVUint(const QByteArray& data)
|
||||
char num3;
|
||||
size_t num = 0;
|
||||
int num2 = 0;
|
||||
int i=0;
|
||||
do
|
||||
{
|
||||
int i = 0;
|
||||
do {
|
||||
num3 = data[i++];
|
||||
num |= static_cast<size_t>(num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
@ -92,7 +85,7 @@ size_t dataToVUint(const QByteArray& data)
|
||||
|
||||
unsigned getVUint32Size(QByteArray data)
|
||||
{
|
||||
unsigned lensize=0;
|
||||
unsigned lensize = 0;
|
||||
|
||||
char num3;
|
||||
do {
|
||||
@ -107,15 +100,14 @@ QByteArray vintToData(int num)
|
||||
{
|
||||
QByteArray data(sizeof(int), 0);
|
||||
// Write the size in a Uint of variable lenght (8-32 bits)
|
||||
int i=0;
|
||||
while (num >= 0x80)
|
||||
{
|
||||
int i = 0;
|
||||
while (num >= 0x80) {
|
||||
data[i] = static_cast<char>(num | 0x80);
|
||||
++i;
|
||||
num = num >> 7;
|
||||
}
|
||||
data[i] = static_cast<char>(num);
|
||||
data.resize(i+1);
|
||||
data.resize(i + 1);
|
||||
return data;
|
||||
}
|
||||
|
||||
@ -123,14 +115,13 @@ QByteArray vuintToData(size_t num)
|
||||
{
|
||||
QByteArray data(sizeof(size_t), 0);
|
||||
// Write the size in a Uint of variable lenght (8-32 bits)
|
||||
int i=0;
|
||||
while (num >= 0x80)
|
||||
{
|
||||
int i = 0;
|
||||
while (num >= 0x80) {
|
||||
data[i] = static_cast<char>(num | 0x80);
|
||||
++i;
|
||||
num = num >> 7;
|
||||
}
|
||||
data[i] = static_cast<char>(num);
|
||||
data.resize(i+1);
|
||||
data.resize(i + 1);
|
||||
return data;
|
||||
}
|
||||
|
@ -21,9 +21,9 @@
|
||||
#ifndef SERIALIZE_H
|
||||
#define SERIALIZE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <cstdint>
|
||||
|
||||
QString dataToString(QByteArray data);
|
||||
uint64_t dataToUint64(const QByteArray& data);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,15 +21,15 @@
|
||||
#ifndef SETTINGS_HPP
|
||||
#define SETTINGS_HPP
|
||||
|
||||
#include "src/core/corestructs.h"
|
||||
#include <QDate>
|
||||
#include <QFlags>
|
||||
#include <QFont>
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
#include <QNetworkProxy>
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
#include <QMutex>
|
||||
#include <QDate>
|
||||
#include <QNetworkProxy>
|
||||
#include <QFlags>
|
||||
#include "src/core/corestructs.h"
|
||||
|
||||
class ToxPk;
|
||||
class Profile;
|
||||
@ -46,100 +46,88 @@ class Settings : public QObject
|
||||
Q_ENUMS(StyleType)
|
||||
|
||||
// general
|
||||
Q_PROPERTY(bool compactLayout READ getCompactLayout WRITE setCompactLayout
|
||||
NOTIFY compactLayoutChanged FINAL)
|
||||
Q_PROPERTY(bool autorun READ getAutorun WRITE setAutorun
|
||||
NOTIFY autorunChanged FINAL)
|
||||
Q_PROPERTY(bool compactLayout READ getCompactLayout WRITE setCompactLayout NOTIFY compactLayoutChanged FINAL)
|
||||
Q_PROPERTY(bool autorun READ getAutorun WRITE setAutorun NOTIFY autorunChanged FINAL)
|
||||
|
||||
// GUI
|
||||
Q_PROPERTY(bool separateWindow READ getSeparateWindow
|
||||
WRITE setSeparateWindow NOTIFY separateWindowChanged FINAL)
|
||||
Q_PROPERTY(QString smileyPack READ getSmileyPack WRITE setSmileyPack
|
||||
NOTIFY smileyPackChanged FINAL)
|
||||
Q_PROPERTY(int emojiFontPointSize READ getEmojiFontPointSize
|
||||
WRITE setEmojiFontPointSize NOTIFY emojiFontPointSizeChanged
|
||||
FINAL)
|
||||
Q_PROPERTY(bool minimizeOnClose READ getMinimizeOnClose
|
||||
WRITE setMinimizeOnClose NOTIFY minimizeOnCloseChanged FINAL)
|
||||
Q_PROPERTY(QByteArray windowGeometry READ getWindowGeometry
|
||||
WRITE setWindowGeometry NOTIFY windowGeometryChanged FINAL)
|
||||
Q_PROPERTY(QByteArray windowState READ getWindowState WRITE setWindowState
|
||||
NOTIFY windowStateChanged FINAL)
|
||||
Q_PROPERTY(QByteArray splitterState READ getSplitterState
|
||||
WRITE setSplitterState NOTIFY splitterStateChanged FINAL)
|
||||
Q_PROPERTY(QByteArray dialogGeometry READ getDialogGeometry
|
||||
WRITE setDialogGeometry NOTIFY dialogGeometryChanged FINAL)
|
||||
Q_PROPERTY(QByteArray dialogSplitterState READ getDialogSplitterState
|
||||
WRITE setDialogSplitterState NOTIFY dialogSplitterStateChanged
|
||||
FINAL)
|
||||
Q_PROPERTY(QByteArray dialogSettingsGeometry READ getDialogSettingsGeometry
|
||||
WRITE setDialogSettingsGeometry
|
||||
NOTIFY dialogSettingsGeometryChanged FINAL)
|
||||
Q_PROPERTY(QString style READ getStyle WRITE setStyle NOTIFY styleChanged
|
||||
FINAL)
|
||||
Q_PROPERTY(bool showSystemTray READ getShowSystemTray
|
||||
WRITE setShowSystemTray NOTIFY showSystemTrayChanged FINAL)
|
||||
Q_PROPERTY(bool separateWindow READ getSeparateWindow WRITE setSeparateWindow NOTIFY
|
||||
separateWindowChanged FINAL)
|
||||
Q_PROPERTY(QString smileyPack READ getSmileyPack WRITE setSmileyPack NOTIFY smileyPackChanged FINAL)
|
||||
Q_PROPERTY(int emojiFontPointSize READ getEmojiFontPointSize WRITE setEmojiFontPointSize NOTIFY
|
||||
emojiFontPointSizeChanged FINAL)
|
||||
Q_PROPERTY(bool minimizeOnClose READ getMinimizeOnClose WRITE setMinimizeOnClose NOTIFY
|
||||
minimizeOnCloseChanged FINAL)
|
||||
Q_PROPERTY(QByteArray windowGeometry READ getWindowGeometry WRITE setWindowGeometry NOTIFY
|
||||
windowGeometryChanged FINAL)
|
||||
Q_PROPERTY(QByteArray windowState READ getWindowState WRITE setWindowState NOTIFY windowStateChanged FINAL)
|
||||
Q_PROPERTY(QByteArray splitterState READ getSplitterState WRITE setSplitterState NOTIFY
|
||||
splitterStateChanged FINAL)
|
||||
Q_PROPERTY(QByteArray dialogGeometry READ getDialogGeometry WRITE setDialogGeometry NOTIFY
|
||||
dialogGeometryChanged FINAL)
|
||||
Q_PROPERTY(QByteArray dialogSplitterState READ getDialogSplitterState WRITE
|
||||
setDialogSplitterState NOTIFY dialogSplitterStateChanged FINAL)
|
||||
Q_PROPERTY(QByteArray dialogSettingsGeometry READ getDialogSettingsGeometry WRITE
|
||||
setDialogSettingsGeometry NOTIFY dialogSettingsGeometryChanged FINAL)
|
||||
Q_PROPERTY(QString style READ getStyle WRITE setStyle NOTIFY styleChanged FINAL)
|
||||
Q_PROPERTY(bool showSystemTray READ getShowSystemTray WRITE setShowSystemTray NOTIFY
|
||||
showSystemTrayChanged FINAL)
|
||||
|
||||
// ChatView
|
||||
Q_PROPERTY(bool groupchatPosition READ getGroupchatPosition
|
||||
WRITE setGroupchatPosition NOTIFY groupchatPositionChanged FINAL)
|
||||
Q_PROPERTY(QFont chatMessageFont READ getChatMessageFont
|
||||
WRITE setChatMessageFont NOTIFY chatMessageFontChanged FINAL)
|
||||
Q_PROPERTY(StyleType stylePreference READ getStylePreference
|
||||
WRITE setStylePreference NOTIFY stylePreferenceChanged FINAL)
|
||||
Q_PROPERTY(QString timestampFormat READ getTimestampFormat
|
||||
WRITE setTimestampFormat NOTIFY timestampFormatChanged FINAL)
|
||||
Q_PROPERTY(QString dateFormat READ getDateFormat WRITE setDateFormat
|
||||
NOTIFY dateFormatChanged FINAL)
|
||||
Q_PROPERTY(bool statusChangeNotificationEnabled
|
||||
READ getStatusChangeNotificationEnabled
|
||||
WRITE setStatusChangeNotificationEnabled
|
||||
NOTIFY statusChangeNotificationEnabledChanged FINAL)
|
||||
Q_PROPERTY(bool groupchatPosition READ getGroupchatPosition WRITE setGroupchatPosition NOTIFY
|
||||
groupchatPositionChanged FINAL)
|
||||
Q_PROPERTY(QFont chatMessageFont READ getChatMessageFont WRITE setChatMessageFont NOTIFY
|
||||
chatMessageFontChanged FINAL)
|
||||
Q_PROPERTY(StyleType stylePreference READ getStylePreference WRITE setStylePreference NOTIFY
|
||||
stylePreferenceChanged FINAL)
|
||||
Q_PROPERTY(QString timestampFormat READ getTimestampFormat WRITE setTimestampFormat NOTIFY
|
||||
timestampFormatChanged FINAL)
|
||||
Q_PROPERTY(QString dateFormat READ getDateFormat WRITE setDateFormat NOTIFY dateFormatChanged FINAL)
|
||||
Q_PROPERTY(bool statusChangeNotificationEnabled READ getStatusChangeNotificationEnabled WRITE
|
||||
setStatusChangeNotificationEnabled NOTIFY statusChangeNotificationEnabledChanged FINAL)
|
||||
|
||||
// Privacy
|
||||
Q_PROPERTY(bool typingNotification READ getTypingNotification
|
||||
WRITE setTypingNotification NOTIFY typingNotificationChanged
|
||||
FINAL)
|
||||
Q_PROPERTY(bool typingNotification READ getTypingNotification WRITE setTypingNotification NOTIFY
|
||||
typingNotificationChanged FINAL)
|
||||
|
||||
// Audio
|
||||
Q_PROPERTY(QString inDev READ getInDev WRITE setInDev
|
||||
NOTIFY inDevChanged FINAL)
|
||||
Q_PROPERTY(bool audioInDevEnabled READ getAudioInDevEnabled
|
||||
WRITE setAudioInDevEnabled NOTIFY audioInDevEnabledChanged FINAL)
|
||||
Q_PROPERTY(qreal audioInGainDecibel READ getAudioInGainDecibel
|
||||
WRITE setAudioInGainDecibel NOTIFY audioInGainDecibelChanged
|
||||
FINAL)
|
||||
Q_PROPERTY(QString outDev READ getOutDev WRITE setOutDev
|
||||
NOTIFY outDevChanged FINAL)
|
||||
Q_PROPERTY(bool audioOutDevEnabled READ getAudioOutDevEnabled
|
||||
WRITE setAudioOutDevEnabled NOTIFY audioOutDevEnabledChanged
|
||||
FINAL)
|
||||
Q_PROPERTY(int outVolume READ getOutVolume WRITE setOutVolume
|
||||
NOTIFY outVolumeChanged FINAL)
|
||||
Q_PROPERTY(QString inDev READ getInDev WRITE setInDev NOTIFY inDevChanged FINAL)
|
||||
Q_PROPERTY(bool audioInDevEnabled READ getAudioInDevEnabled WRITE setAudioInDevEnabled NOTIFY
|
||||
audioInDevEnabledChanged FINAL)
|
||||
Q_PROPERTY(qreal audioInGainDecibel READ getAudioInGainDecibel WRITE setAudioInGainDecibel
|
||||
NOTIFY audioInGainDecibelChanged FINAL)
|
||||
Q_PROPERTY(QString outDev READ getOutDev WRITE setOutDev NOTIFY outDevChanged FINAL)
|
||||
Q_PROPERTY(bool audioOutDevEnabled READ getAudioOutDevEnabled WRITE setAudioOutDevEnabled NOTIFY
|
||||
audioOutDevEnabledChanged FINAL)
|
||||
Q_PROPERTY(int outVolume READ getOutVolume WRITE setOutVolume NOTIFY outVolumeChanged FINAL)
|
||||
|
||||
// Video
|
||||
Q_PROPERTY(QString videoDev READ getVideoDev WRITE setVideoDev
|
||||
NOTIFY videoDevChanged FINAL)
|
||||
Q_PROPERTY(QRect camVideoRes READ getCamVideoRes WRITE setCamVideoRes
|
||||
NOTIFY camVideoResChanged FINAL)
|
||||
Q_PROPERTY(QRect screenRegion READ getScreenRegion WRITE setScreenRegion
|
||||
NOTIFY screenRegionChanged FINAL)
|
||||
Q_PROPERTY(bool screenGrabbed READ getScreenGrabbed WRITE setScreenGrabbed
|
||||
NOTIFY screenGrabbedChanged FINAL)
|
||||
Q_PROPERTY(quint16 camVideoFPS READ getCamVideoFPS
|
||||
WRITE setCamVideoFPS NOTIFY camVideoFPSChanged FINAL)
|
||||
Q_PROPERTY(QString videoDev READ getVideoDev WRITE setVideoDev NOTIFY videoDevChanged FINAL)
|
||||
Q_PROPERTY(QRect camVideoRes READ getCamVideoRes WRITE setCamVideoRes NOTIFY camVideoResChanged FINAL)
|
||||
Q_PROPERTY(QRect screenRegion READ getScreenRegion WRITE setScreenRegion NOTIFY screenRegionChanged FINAL)
|
||||
Q_PROPERTY(bool screenGrabbed READ getScreenGrabbed WRITE setScreenGrabbed NOTIFY screenGrabbedChanged FINAL)
|
||||
Q_PROPERTY(quint16 camVideoFPS READ getCamVideoFPS WRITE setCamVideoFPS NOTIFY camVideoFPSChanged FINAL)
|
||||
|
||||
public:
|
||||
enum class ProxyType {ptNone = 0, ptSOCKS5 = 1, ptHTTP = 2};
|
||||
enum class StyleType {NONE = 0, WITH_CHARS = 1, WITHOUT_CHARS = 2};
|
||||
enum class ProxyType
|
||||
{
|
||||
ptNone = 0,
|
||||
ptSOCKS5 = 1,
|
||||
ptHTTP = 2
|
||||
};
|
||||
enum class StyleType
|
||||
{
|
||||
NONE = 0,
|
||||
WITH_CHARS = 1,
|
||||
WITHOUT_CHARS = 2
|
||||
};
|
||||
enum class AutoAcceptCall
|
||||
{
|
||||
None = 0x00,
|
||||
Audio = 0x01,
|
||||
Video = 0x02,
|
||||
AV = Audio | Video
|
||||
};
|
||||
Q_DECLARE_FLAGS(AutoAcceptCallFlags, AutoAcceptCall)
|
||||
{
|
||||
None = 0x00,
|
||||
Audio = 0x01,
|
||||
Video = 0x02,
|
||||
AV = Audio | Video
|
||||
};
|
||||
Q_DECLARE_FLAGS(AutoAcceptCallFlags, AutoAcceptCall)
|
||||
|
||||
public:
|
||||
static Settings& getInstance();
|
||||
@ -468,7 +456,7 @@ public:
|
||||
void setFriendCircleID(const ToxPk& id, int circleID);
|
||||
|
||||
QDate getFriendActivity(const ToxPk& id) const;
|
||||
void setFriendActivity(const ToxPk& id, const QDate &date);
|
||||
void setFriendActivity(const ToxPk& id, const QDate& date);
|
||||
|
||||
void removeFriendSettings(const ToxPk& id);
|
||||
|
||||
@ -529,7 +517,7 @@ public:
|
||||
private:
|
||||
Settings();
|
||||
~Settings();
|
||||
Settings(Settings &settings) = delete;
|
||||
Settings(Settings& settings) = delete;
|
||||
Settings& operator=(const Settings&) = delete;
|
||||
|
||||
private slots:
|
||||
|
@ -23,11 +23,11 @@
|
||||
#include "src/core/toxencrypt.h"
|
||||
#include "src/persistence/profile.h"
|
||||
|
||||
#include <QSaveFile>
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
#include <memory>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @class SettingsSerializer
|
||||
@ -58,7 +58,7 @@ enum class RecordTag : uint8_t
|
||||
* @var static const char magic[];
|
||||
* @brief Little endian ASCII "QTOX" magic
|
||||
*/
|
||||
const char SettingsSerializer::magic[] = {0x51,0x54,0x4F,0x58};
|
||||
const char SettingsSerializer::magic[] = {0x51, 0x54, 0x4F, 0x58};
|
||||
|
||||
QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag)
|
||||
{
|
||||
@ -80,7 +80,7 @@ QDataStream& writeStream(QDataStream& dataStream, const QString& str)
|
||||
|
||||
QDataStream& readStream(QDataStream& dataStream, SettingsSerializer::RecordTag& tag)
|
||||
{
|
||||
return dataStream >> reinterpret_cast<quint8&>(tag) ;
|
||||
return dataStream >> reinterpret_cast<quint8&>(tag);
|
||||
}
|
||||
|
||||
|
||||
@ -89,8 +89,7 @@ QDataStream& readStream(QDataStream& dataStream, QByteArray& data)
|
||||
char num3;
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
do
|
||||
{
|
||||
do {
|
||||
dataStream.readRawData(&num3, 1);
|
||||
num |= (num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
@ -100,23 +99,23 @@ QDataStream& readStream(QDataStream& dataStream, QByteArray& data)
|
||||
return dataStream;
|
||||
}
|
||||
|
||||
SettingsSerializer::SettingsSerializer(QString filePath, const QString &password)
|
||||
: path{filePath}, password{password},
|
||||
group{-1}, array{-1}, arrayIndex{-1}
|
||||
SettingsSerializer::SettingsSerializer(QString filePath, const QString& password)
|
||||
: path{filePath}
|
||||
, password{password}
|
||||
, group{-1}
|
||||
, array{-1}
|
||||
, arrayIndex{-1}
|
||||
{
|
||||
}
|
||||
|
||||
void SettingsSerializer::beginGroup(const QString &prefix)
|
||||
void SettingsSerializer::beginGroup(const QString& prefix)
|
||||
{
|
||||
if (prefix.isEmpty())
|
||||
endGroup();
|
||||
int index = groups.indexOf(prefix);
|
||||
if (index >= 0)
|
||||
{
|
||||
if (index >= 0) {
|
||||
group = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
group = groups.size();
|
||||
groups.append(prefix);
|
||||
}
|
||||
@ -127,22 +126,16 @@ void SettingsSerializer::endGroup()
|
||||
group = -1;
|
||||
}
|
||||
|
||||
int SettingsSerializer::beginReadArray(const QString &prefix)
|
||||
int SettingsSerializer::beginReadArray(const QString& prefix)
|
||||
{
|
||||
auto index = std::find_if(std::begin(arrays), std::end(arrays),
|
||||
[=](const Array& a)
|
||||
{
|
||||
return a.name==prefix;
|
||||
});
|
||||
[=](const Array& a) { return a.name == prefix; });
|
||||
|
||||
if (index != std::end(arrays))
|
||||
{
|
||||
if (index != std::end(arrays)) {
|
||||
array = static_cast<int>(index - std::begin(arrays));
|
||||
arrayIndex = -1;
|
||||
return index->size;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
array = arrays.size();
|
||||
arrays.push_back({group, 0, prefix, {}});
|
||||
arrayIndex = -1;
|
||||
@ -150,23 +143,17 @@ int SettingsSerializer::beginReadArray(const QString &prefix)
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsSerializer::beginWriteArray(const QString &prefix, int size)
|
||||
void SettingsSerializer::beginWriteArray(const QString& prefix, int size)
|
||||
{
|
||||
auto index = std::find_if(std::begin(arrays), std::end(arrays),
|
||||
[=](const Array& a)
|
||||
{
|
||||
return a.name==prefix;
|
||||
});
|
||||
[=](const Array& a) { return a.name == prefix; });
|
||||
|
||||
if (index != std::end(arrays))
|
||||
{
|
||||
if (index != std::end(arrays)) {
|
||||
array = static_cast<int>(index - std::begin(arrays));
|
||||
arrayIndex = -1;
|
||||
if (size > 0)
|
||||
index->size = std::max(index->size, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if (size < 0)
|
||||
size = 0;
|
||||
array = arrays.size();
|
||||
@ -185,15 +172,12 @@ void SettingsSerializer::setArrayIndex(int i)
|
||||
arrayIndex = i;
|
||||
}
|
||||
|
||||
void SettingsSerializer::setValue(const QString &key, const QVariant &value)
|
||||
void SettingsSerializer::setValue(const QString& key, const QVariant& value)
|
||||
{
|
||||
Value* v = findValue(key);
|
||||
if (v)
|
||||
{
|
||||
if (v) {
|
||||
v->value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
Value nv{group, array, arrayIndex, key, value};
|
||||
if (array >= 0)
|
||||
arrays[array].values.append(values.size());
|
||||
@ -201,7 +185,7 @@ void SettingsSerializer::setValue(const QString &key, const QVariant &value)
|
||||
}
|
||||
}
|
||||
|
||||
QVariant SettingsSerializer::value(const QString &key, const QVariant &defaultValue) const
|
||||
QVariant SettingsSerializer::value(const QString& key, const QVariant& defaultValue) const
|
||||
{
|
||||
const Value* v = findValue(key);
|
||||
if (v)
|
||||
@ -212,23 +196,18 @@ QVariant SettingsSerializer::value(const QString &key, const QVariant &defaultVa
|
||||
|
||||
const SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key) const
|
||||
{
|
||||
if (array != -1)
|
||||
{
|
||||
for (const Array& a : arrays)
|
||||
{
|
||||
if (array != -1) {
|
||||
for (const Array& a : arrays) {
|
||||
if (a.group != group)
|
||||
continue;
|
||||
|
||||
for (int vi : a.values)
|
||||
{
|
||||
for (int vi : a.values) {
|
||||
const Value& v = values[vi];
|
||||
if (v.arrayIndex == arrayIndex && v.key == key)
|
||||
return &v;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
for (const Value& v : values)
|
||||
if (v.group == group && v.array == -1 && v.key == key)
|
||||
return &v;
|
||||
@ -274,8 +253,7 @@ void SettingsSerializer::load()
|
||||
void SettingsSerializer::save()
|
||||
{
|
||||
QSaveFile f(path);
|
||||
if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly))
|
||||
{
|
||||
if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
|
||||
qWarning() << "Couldn't open file";
|
||||
return;
|
||||
}
|
||||
@ -284,18 +262,15 @@ void SettingsSerializer::save()
|
||||
QDataStream stream(&data, QIODevice::ReadWrite | QIODevice::Append);
|
||||
stream.setVersion(QDataStream::Qt_5_0);
|
||||
|
||||
for (int g=-1; g<groups.size(); ++g)
|
||||
{
|
||||
for (int g = -1; g < groups.size(); ++g) {
|
||||
// Save the group name, if any
|
||||
if (g!=-1)
|
||||
{
|
||||
if (g != -1) {
|
||||
writeStream(stream, RecordTag::GroupStart);
|
||||
writeStream(stream, groups[g].toUtf8());
|
||||
}
|
||||
|
||||
// Save all the arrays of this group
|
||||
for (const Array& a : arrays)
|
||||
{
|
||||
for (const Array& a : arrays) {
|
||||
if (a.group != g)
|
||||
continue;
|
||||
if (a.size <= 0)
|
||||
@ -304,8 +279,7 @@ void SettingsSerializer::save()
|
||||
writeStream(stream, a.name.toUtf8());
|
||||
writeStream(stream, vintToData(a.size));
|
||||
|
||||
for (int vi : a.values)
|
||||
{
|
||||
for (int vi : a.values) {
|
||||
const Value& v = values[vi];
|
||||
writeStream(stream, RecordTag::ArrayValue);
|
||||
writeStream(stream, vintToData(values[vi].arrayIndex));
|
||||
@ -316,8 +290,7 @@ void SettingsSerializer::save()
|
||||
}
|
||||
|
||||
// Save all the values of this group that aren't in an array
|
||||
for (const Value& v : values)
|
||||
{
|
||||
for (const Value& v : values) {
|
||||
if (v.group != g || v.array != -1)
|
||||
continue;
|
||||
writeStream(stream, RecordTag::Value);
|
||||
@ -327,8 +300,7 @@ void SettingsSerializer::save()
|
||||
}
|
||||
|
||||
// Encrypt
|
||||
if (!password.isEmpty())
|
||||
{
|
||||
if (!password.isEmpty()) {
|
||||
// TODO: use passkey
|
||||
data = ToxEncrypt::encryptPass(password, data);
|
||||
}
|
||||
@ -336,12 +308,9 @@ void SettingsSerializer::save()
|
||||
f.write(data);
|
||||
|
||||
// check if everything got written
|
||||
if (f.flush())
|
||||
{
|
||||
if (f.flush()) {
|
||||
f.commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
f.cancelWriting();
|
||||
qCritical() << "Failed to write, can't save!";
|
||||
}
|
||||
@ -350,8 +319,7 @@ void SettingsSerializer::save()
|
||||
void SettingsSerializer::readSerialized()
|
||||
{
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Couldn't open file";
|
||||
return;
|
||||
}
|
||||
@ -359,29 +327,23 @@ void SettingsSerializer::readSerialized()
|
||||
f.close();
|
||||
|
||||
// Decrypt
|
||||
if (tox_is_data_encrypted(reinterpret_cast<uint8_t*>(data.data())))
|
||||
{
|
||||
if (password.isEmpty())
|
||||
{
|
||||
if (tox_is_data_encrypted(reinterpret_cast<uint8_t*>(data.data()))) {
|
||||
if (password.isEmpty()) {
|
||||
qCritical() << "The settings file is encrypted, but we don't have a password!";
|
||||
return;
|
||||
}
|
||||
|
||||
data = ToxEncrypt::decryptPass(password, data);
|
||||
if (data.isEmpty())
|
||||
{
|
||||
if (data.isEmpty()) {
|
||||
qCritical() << "Failed to decrypt the settings file";
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if (!password.isEmpty())
|
||||
qWarning() << "We have a password, but the settings file is not encrypted";
|
||||
}
|
||||
|
||||
if (memcmp(data.data(), magic, 4))
|
||||
{
|
||||
if (memcmp(data.data(), magic, 4)) {
|
||||
qWarning() << "Bad magic!";
|
||||
return;
|
||||
}
|
||||
@ -390,45 +352,35 @@ void SettingsSerializer::readSerialized()
|
||||
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||
stream.setVersion(QDataStream::Qt_5_0);
|
||||
|
||||
while (!stream.atEnd())
|
||||
{
|
||||
while (!stream.atEnd()) {
|
||||
RecordTag tag;
|
||||
readStream(stream, tag);
|
||||
if (tag == RecordTag::Value)
|
||||
{
|
||||
if (tag == RecordTag::Value) {
|
||||
QByteArray key;
|
||||
QByteArray value;
|
||||
readStream(stream, key);
|
||||
readStream(stream, value);
|
||||
setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value)));
|
||||
}
|
||||
else if (tag == RecordTag::GroupStart)
|
||||
{
|
||||
} else if (tag == RecordTag::GroupStart) {
|
||||
QByteArray prefix;
|
||||
readStream(stream, prefix);
|
||||
beginGroup(QString::fromUtf8(prefix));
|
||||
}
|
||||
else if (tag == RecordTag::ArrayStart)
|
||||
{
|
||||
} else if (tag == RecordTag::ArrayStart) {
|
||||
QByteArray prefix;
|
||||
readStream(stream, prefix);
|
||||
beginReadArray(QString::fromUtf8(prefix));
|
||||
QByteArray sizeData;
|
||||
readStream(stream, sizeData);
|
||||
if (sizeData.isEmpty())
|
||||
{
|
||||
if (sizeData.isEmpty()) {
|
||||
qWarning("The personal save file is corrupted!");
|
||||
return;
|
||||
}
|
||||
int size = dataToVInt(sizeData);
|
||||
arrays[array].size = qMax(size, arrays[array].size);
|
||||
}
|
||||
else if (tag == RecordTag::ArrayValue)
|
||||
{
|
||||
} else if (tag == RecordTag::ArrayValue) {
|
||||
QByteArray indexData;
|
||||
readStream(stream, indexData);
|
||||
if (indexData.isEmpty())
|
||||
{
|
||||
if (indexData.isEmpty()) {
|
||||
qWarning("The personal save file is corrupted!");
|
||||
return;
|
||||
}
|
||||
@ -438,9 +390,7 @@ void SettingsSerializer::readSerialized()
|
||||
readStream(stream, key);
|
||||
readStream(stream, value);
|
||||
setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value)));
|
||||
}
|
||||
else if (tag == RecordTag::ArrayEnd)
|
||||
{
|
||||
} else if (tag == RecordTag::ArrayEnd) {
|
||||
endArray();
|
||||
}
|
||||
}
|
||||
@ -459,8 +409,7 @@ void SettingsSerializer::readIni()
|
||||
if (!s.group().isEmpty())
|
||||
beginGroup(s.group());
|
||||
|
||||
for (QString k : s.childKeys())
|
||||
{
|
||||
for (QString k : s.childKeys()) {
|
||||
setValue(k, s.value(k));
|
||||
}
|
||||
|
||||
@ -470,18 +419,14 @@ void SettingsSerializer::readIni()
|
||||
gstack.push_back(g);
|
||||
|
||||
// Visit the next group, if any
|
||||
while (!gstack.isEmpty())
|
||||
{
|
||||
while (!gstack.isEmpty()) {
|
||||
QString g = gstack.takeLast();
|
||||
if (g.isEmpty())
|
||||
{
|
||||
if (g.isEmpty()) {
|
||||
if (gstack.isEmpty())
|
||||
break;
|
||||
else
|
||||
s.endGroup();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
s.beginGroup(g);
|
||||
break;
|
||||
}
|
||||
@ -495,8 +440,7 @@ void SettingsSerializer::readIni()
|
||||
// Find groups that only have 1 key
|
||||
std::unique_ptr<int[]> groupSizes{new int[groups.size()]};
|
||||
memset(groupSizes.get(), 0, static_cast<size_t>(groups.size()) * sizeof(int));
|
||||
for (const Value& v : values)
|
||||
{
|
||||
for (const Value& v : values) {
|
||||
if (v.group < 0 || v.group > groups.size())
|
||||
continue;
|
||||
groupSizes[static_cast<size_t>(v.group)]++;
|
||||
@ -504,8 +448,7 @@ void SettingsSerializer::readIni()
|
||||
|
||||
// Find arrays, remove their size key from the values, and add them to `arrays`
|
||||
QVector<int> groupsToKill;
|
||||
for (int i=values.size()-1; i>=0; i--)
|
||||
{
|
||||
for (int i = values.size() - 1; i >= 0; i--) {
|
||||
const Value& v = values[i];
|
||||
if (v.group < 0 || v.group > groups.size())
|
||||
continue;
|
||||
@ -519,20 +462,16 @@ void SettingsSerializer::readIni()
|
||||
Array a;
|
||||
a.size = v.value.toInt();
|
||||
int slashIndex = groups[static_cast<int>(v.group)].lastIndexOf('/');
|
||||
if (slashIndex == -1)
|
||||
{
|
||||
if (slashIndex == -1) {
|
||||
a.group = -1;
|
||||
a.name = groups[static_cast<int>(v.group)];
|
||||
a.size = v.value.toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
a.group = -1;
|
||||
for (int i=0; i<groups.size(); ++i)
|
||||
for (int i = 0; i < groups.size(); ++i)
|
||||
if (groups[i] == groups[static_cast<int>(v.group)].left(slashIndex))
|
||||
a.group = i;
|
||||
a.name = groups[static_cast<int>(v.group)].mid(slashIndex+1);
|
||||
|
||||
a.name = groups[static_cast<int>(v.group)].mid(slashIndex + 1);
|
||||
}
|
||||
groupSizes[static_cast<size_t>(v.group)]--;
|
||||
groupsToKill.append(static_cast<int>(v.group));
|
||||
@ -541,17 +480,15 @@ void SettingsSerializer::readIni()
|
||||
}
|
||||
|
||||
// Associate each array's values with the array
|
||||
for (int ai=0; ai<arrays.size(); ++ai)
|
||||
{
|
||||
for (int ai = 0; ai < arrays.size(); ++ai) {
|
||||
Array& a = arrays[ai];
|
||||
QString arrayPrefix;
|
||||
if (a.group != -1)
|
||||
arrayPrefix += groups[static_cast<int>(a.group)]+'/';
|
||||
arrayPrefix += a.name+'/';
|
||||
arrayPrefix += groups[static_cast<int>(a.group)] + '/';
|
||||
arrayPrefix += a.name + '/';
|
||||
|
||||
// Find groups which represent each array index
|
||||
for (int g=0; g<groups.size(); ++g)
|
||||
{
|
||||
for (int g = 0; g < groups.size(); ++g) {
|
||||
if (!groups[g].startsWith(arrayPrefix))
|
||||
continue;
|
||||
bool ok;
|
||||
@ -564,8 +501,7 @@ void SettingsSerializer::readIni()
|
||||
a.size = groupArrayIndex;
|
||||
|
||||
// Associate the values for this array index
|
||||
for (int vi = values.size() - 1; vi >= 0; vi--)
|
||||
{
|
||||
for (int vi = values.size() - 1; vi >= 0; vi--) {
|
||||
Value& v = values[vi];
|
||||
if (v.group != g)
|
||||
continue;
|
||||
@ -579,11 +515,9 @@ void SettingsSerializer::readIni()
|
||||
}
|
||||
|
||||
// Clean up spurious array element groups
|
||||
std::sort(std::begin(groupsToKill), std::end(groupsToKill),
|
||||
std::greater_equal<int>());
|
||||
std::sort(std::begin(groupsToKill), std::end(groupsToKill), std::greater_equal<int>());
|
||||
|
||||
for (int g : groupsToKill)
|
||||
{
|
||||
for (int g : groupsToKill) {
|
||||
if (groupSizes[static_cast<size_t>(g)])
|
||||
continue;
|
||||
|
||||
@ -600,15 +534,13 @@ void SettingsSerializer::readIni()
|
||||
*/
|
||||
void SettingsSerializer::removeGroup(int group)
|
||||
{
|
||||
assert(group<groups.size());
|
||||
for (Array& a : arrays)
|
||||
{
|
||||
assert(group < groups.size());
|
||||
for (Array& a : arrays) {
|
||||
assert(a.group != group);
|
||||
if (a.group > group)
|
||||
a.group--;
|
||||
}
|
||||
for (Value& v : values)
|
||||
{
|
||||
for (Value& v : values) {
|
||||
assert(v.group != group);
|
||||
if (v.group > group)
|
||||
v.group--;
|
||||
|
@ -20,49 +20,62 @@
|
||||
#ifndef SETTINGSSERIALIZER_H
|
||||
#define SETTINGSSERIALIZER_H
|
||||
|
||||
#include <QSettings>
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QDataStream>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class SettingsSerializer
|
||||
{
|
||||
public:
|
||||
SettingsSerializer(QString filePath, const QString &password=QString());
|
||||
SettingsSerializer(QString filePath, const QString& password = QString());
|
||||
|
||||
static bool isSerializedFormat(QString filePath);
|
||||
|
||||
void load();
|
||||
void save();
|
||||
|
||||
void beginGroup(const QString &prefix);
|
||||
void beginGroup(const QString& prefix);
|
||||
void endGroup();
|
||||
|
||||
int beginReadArray(const QString &prefix);
|
||||
void beginWriteArray(const QString &prefix, int size = -1);
|
||||
int beginReadArray(const QString& prefix);
|
||||
void beginWriteArray(const QString& prefix, int size = -1);
|
||||
void endArray();
|
||||
void setArrayIndex(int i);
|
||||
|
||||
void setValue(const QString &key, const QVariant &value);
|
||||
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
void setValue(const QString& key, const QVariant& value);
|
||||
QVariant value(const QString& key, const QVariant& defaultValue = QVariant()) const;
|
||||
|
||||
private:
|
||||
enum class RecordTag : uint8_t
|
||||
{
|
||||
Value=0,
|
||||
GroupStart=1,
|
||||
ArrayStart=2,
|
||||
ArrayValue=3,
|
||||
ArrayEnd=4,
|
||||
Value = 0,
|
||||
GroupStart = 1,
|
||||
ArrayStart = 2,
|
||||
ArrayValue = 3,
|
||||
ArrayEnd = 4,
|
||||
};
|
||||
friend QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag);
|
||||
friend QDataStream& readStream(QDataStream& dataStream, SettingsSerializer::RecordTag& tag);
|
||||
|
||||
struct Value
|
||||
{
|
||||
Value() : group{-2},array{-2},arrayIndex{-2},key{QString()},value{}{}
|
||||
Value()
|
||||
: group{-2}
|
||||
, array{-2}
|
||||
, arrayIndex{-2}
|
||||
, key{QString()}
|
||||
, value{}
|
||||
{
|
||||
}
|
||||
Value(qint64 group, qint64 array, int arrayIndex, QString key, QVariant value)
|
||||
: group{group}, array{array}, arrayIndex{arrayIndex}, key{key}, value{value} {}
|
||||
: group{group}
|
||||
, array{array}
|
||||
, arrayIndex{arrayIndex}
|
||||
, key{key}
|
||||
, value{value}
|
||||
{
|
||||
}
|
||||
qint64 group;
|
||||
qint64 array;
|
||||
int arrayIndex;
|
||||
@ -79,8 +92,8 @@ private:
|
||||
};
|
||||
|
||||
private:
|
||||
const Value *findValue(const QString& key) const;
|
||||
Value *findValue(const QString& key);
|
||||
const Value* findValue(const QString& key) const;
|
||||
Value* findValue(const QString& key);
|
||||
void readSerialized();
|
||||
void readIni();
|
||||
void removeValue(const QString& key);
|
||||
|
@ -22,9 +22,14 @@
|
||||
#include "src/widget/style.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QBuffer>
|
||||
#include <QCoreApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QDir>
|
||||
#include <QDomDocument>
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
#include <QDomElement>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
@ -32,11 +37,6 @@
|
||||
#include <QFontInfo>
|
||||
#include <QIcon>
|
||||
#include <QPixmap>
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
#include <QBuffer>
|
||||
#include <QStandardPaths>
|
||||
#include <QStringBuilder>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
@ -73,8 +73,8 @@ SmileyPack::SmileyPack()
|
||||
{
|
||||
loadingMutex.lock();
|
||||
QtConcurrent::run(this, &SmileyPack::load, Settings::getInstance().getSmileyPack());
|
||||
connect(&Settings::getInstance(), &Settings::smileyPackChanged,
|
||||
this, &SmileyPack::onSmileyPackChanged);
|
||||
connect(&Settings::getInstance(), &Settings::smileyPackChanged, this,
|
||||
&SmileyPack::onSmileyPackChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,7 +93,8 @@ QStringList SmileyPack::loadDefaultPaths()
|
||||
// Workaround to fix https://bugreports.qt.io/browse/QTBUG-57522
|
||||
setlocale(LC_ALL, "");
|
||||
#endif
|
||||
QStringList paths = QStringList{":/smileys", "~/.kde4/share/emoticons", "~/.kde/share/emoticons"};
|
||||
QStringList paths =
|
||||
QStringList{":/smileys", "~/.kde4/share/emoticons", "~/.kde/share/emoticons"};
|
||||
// qTox should find emoticons next to the binary
|
||||
paths.append('.' + QDir::separator() + EMOTICONS_SUB_DIR);
|
||||
|
||||
@ -105,21 +106,17 @@ QStringList SmileyPack::loadDefaultPaths()
|
||||
#warning "Qt < 5.4.0 has a trouble with unicode symbols in path on few systems"
|
||||
location = QStandardPaths::DataLocation;
|
||||
#endif
|
||||
for(auto qtoxPath : QStandardPaths::standardLocations(location))
|
||||
{
|
||||
for (auto qtoxPath : QStandardPaths::standardLocations(location)) {
|
||||
qtoxPath += QDir::separator() + EMOTICONS_SUB_DIR;
|
||||
if(!paths.contains(qtoxPath))
|
||||
{
|
||||
if (!paths.contains(qtoxPath)) {
|
||||
paths << qtoxPath;
|
||||
}
|
||||
}
|
||||
|
||||
// system wide emoticons
|
||||
for(auto genericPath : QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation))
|
||||
{
|
||||
for (auto genericPath : QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) {
|
||||
genericPath += QDir::separator() + EMOTICONS_SUB_DIR;
|
||||
if(!paths.contains(genericPath))
|
||||
{
|
||||
if (!paths.contains(genericPath)) {
|
||||
paths << genericPath;
|
||||
}
|
||||
}
|
||||
@ -134,33 +131,27 @@ QList<QPair<QString, QString>> SmileyPack::listSmileyPacks()
|
||||
|
||||
QList<QPair<QString, QString>> SmileyPack::listSmileyPacks(const QStringList& paths)
|
||||
{
|
||||
QList<QPair<QString, QString> > smileyPacks;
|
||||
QList<QPair<QString, QString>> smileyPacks;
|
||||
|
||||
for (QString path : paths)
|
||||
{
|
||||
if (path.leftRef(1) == "~")
|
||||
{
|
||||
for (QString path : paths) {
|
||||
if (path.leftRef(1) == "~") {
|
||||
path.replace(0, 1, QDir::homePath());
|
||||
}
|
||||
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
{
|
||||
if (!dir.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const QString& subdirectory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
{
|
||||
for (const QString& subdirectory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||
dir.cd(subdirectory);
|
||||
|
||||
QFileInfoList entries = dir.entryInfoList(QStringList() << "emoticons.xml", QDir::Files);
|
||||
// Does it contain a file called emoticons.xml?
|
||||
if (entries.size() > 0)
|
||||
{
|
||||
if (entries.size() > 0) {
|
||||
QString packageName = dir.dirName();
|
||||
QString absPath = entries[0].absoluteFilePath();
|
||||
if (!smileyPacks.contains(QPair<QString, QString>(packageName, absPath)))
|
||||
{
|
||||
if (!smileyPacks.contains(QPair<QString, QString>(packageName, absPath))) {
|
||||
smileyPacks << QPair<QString, QString>(packageName, absPath);
|
||||
}
|
||||
}
|
||||
@ -193,8 +184,7 @@ bool SmileyPack::load(const QString& filename)
|
||||
|
||||
// open emoticons.xml
|
||||
QFile xmlFile(filename);
|
||||
if (!xmlFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!xmlFile.open(QIODevice::ReadOnly)) {
|
||||
loadingMutex.unlock();
|
||||
return false; // cannot open file
|
||||
}
|
||||
@ -220,32 +210,26 @@ bool SmileyPack::load(const QString& filename)
|
||||
doc.setContent(xmlFile.readAll());
|
||||
|
||||
QDomNodeList emoticonElements = doc.elementsByTagName("emoticon");
|
||||
for (int i = 0; i < emoticonElements.size(); ++i)
|
||||
{
|
||||
for (int i = 0; i < emoticonElements.size(); ++i) {
|
||||
QString file = emoticonElements.at(i).attributes().namedItem("file").nodeValue();
|
||||
QDomElement stringElement = emoticonElements.at(i).firstChildElement("string");
|
||||
|
||||
QStringList emoticonSet; // { ":)", ":-)" } etc.
|
||||
|
||||
while (!stringElement.isNull())
|
||||
{
|
||||
QString emoticon = stringElement.text()
|
||||
.replace("<","<").replace(">",">");
|
||||
while (!stringElement.isNull()) {
|
||||
QString emoticon = stringElement.text().replace("<", "<").replace(">", ">");
|
||||
filenameTable.insert(emoticon, file);
|
||||
|
||||
cacheSmiley(file); // preload all smileys
|
||||
|
||||
if (!getCachedSmiley(emoticon).isNull())
|
||||
{
|
||||
if (!getCachedSmiley(emoticon).isNull()) {
|
||||
emoticonSet.push_back(emoticon);
|
||||
}
|
||||
|
||||
stringElement = stringElement.nextSibling().toElement();
|
||||
|
||||
}
|
||||
|
||||
if (emoticonSet.size() > 0)
|
||||
{
|
||||
if (emoticonSet.size() > 0) {
|
||||
emoticons.push_back(emoticonSet);
|
||||
}
|
||||
}
|
||||
@ -264,11 +248,9 @@ QString SmileyPack::smileyfied(QString msg)
|
||||
int index = msg.indexOf(exp);
|
||||
|
||||
// if a word is key of a smiley, replace it by its corresponding image in Rich Text
|
||||
while (index >= 0)
|
||||
{
|
||||
while (index >= 0) {
|
||||
QString key = exp.cap();
|
||||
if (filenameTable.contains(key))
|
||||
{
|
||||
if (filenameTable.contains(key)) {
|
||||
QString imgRichText = getAsRichText(key);
|
||||
|
||||
msg.replace(index, key.length(), imgRichText);
|
||||
@ -309,15 +291,13 @@ void SmileyPack::cacheSmiley(const QString& name)
|
||||
QIcon SmileyPack::getCachedSmiley(const QString& key)
|
||||
{
|
||||
// valid key?
|
||||
if (!filenameTable.contains(key))
|
||||
{
|
||||
if (!filenameTable.contains(key)) {
|
||||
return QPixmap();
|
||||
}
|
||||
|
||||
// cache it if needed
|
||||
QString file = filenameTable.value(key);
|
||||
if (!iconCache.contains(file))
|
||||
{
|
||||
if (!iconCache.contains(file)) {
|
||||
cacheSmiley(file);
|
||||
}
|
||||
|
||||
|
@ -21,11 +21,11 @@
|
||||
#define SMILEYPACK_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QIcon>
|
||||
#include <QMutex>
|
||||
|
||||
class SmileyPack : public QObject
|
||||
{
|
||||
|
@ -18,9 +18,9 @@
|
||||
*/
|
||||
|
||||
#include "toxsave.h"
|
||||
#include "src/widget/gui.h"
|
||||
#include "src/core/core.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include "src/widget/gui.h"
|
||||
#include "src/widget/tool/profileimporter.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
@ -44,8 +44,7 @@ bool handleToxSave(const QString& path)
|
||||
{
|
||||
Core* core = Core::getInstance();
|
||||
|
||||
while (!core)
|
||||
{
|
||||
while (!core) {
|
||||
core = Core::getInstance();
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
@ -23,10 +23,9 @@
|
||||
#define PLATFORM_AUTORUN_H
|
||||
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
bool setAutorun(bool on);
|
||||
bool getAutorun();
|
||||
namespace Platform {
|
||||
bool setAutorun(bool on);
|
||||
bool getAutorun();
|
||||
}
|
||||
|
||||
#endif // PLATFORM_AUTORUN_H
|
||||
|
@ -19,21 +19,24 @@
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#include "src/platform/autorun.h"
|
||||
#include <QSettings>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QCoreApplication>
|
||||
|
||||
int state ;
|
||||
int state;
|
||||
|
||||
bool Platform::setAutorun(bool on)
|
||||
{
|
||||
QString qtoxPlist = QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QDir::separator() +
|
||||
"Library" + QDir::separator() + "LaunchAgents" + QDir::separator() + "chat.tox.qtox.autorun.plist");
|
||||
QString qtoxDir = QDir::cleanPath(QCoreApplication::applicationDirPath() + QDir::separator() + "qtox");
|
||||
QString qtoxPlist =
|
||||
QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
|
||||
+ QDir::separator() + "Library" + QDir::separator() + "LaunchAgents"
|
||||
+ QDir::separator() + "chat.tox.qtox.autorun.plist");
|
||||
QString qtoxDir =
|
||||
QDir::cleanPath(QCoreApplication::applicationDirPath() + QDir::separator() + "qtox");
|
||||
QSettings autoRun(qtoxPlist, QSettings::NativeFormat);
|
||||
autoRun.setValue("Label","chat.tox.qtox.autorun");
|
||||
autoRun.setValue("Label", "chat.tox.qtox.autorun");
|
||||
autoRun.setValue("Program", qtoxDir);
|
||||
|
||||
state = on;
|
||||
@ -46,4 +49,4 @@ bool Platform::getAutorun()
|
||||
return state;
|
||||
}
|
||||
|
||||
#endif // defined(__APPLE__) && defined(__MACH__)
|
||||
#endif // defined(__APPLE__) && defined(__MACH__)
|
||||
|
@ -19,10 +19,10 @@
|
||||
|
||||
#include <QApplication>
|
||||
#ifdef Q_OS_WIN32
|
||||
#include "src/platform/autorun.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include <windows.h>
|
||||
#include "src/platform/autorun.h"
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef UNICODE
|
||||
/**
|
||||
@ -31,43 +31,48 @@
|
||||
* easier to reuse and compatible with both setups.
|
||||
*/
|
||||
using tstring = std::wstring;
|
||||
static inline tstring toTString(QString s) { return s.toStdWString(); }
|
||||
static inline tstring toTString(QString s)
|
||||
{
|
||||
return s.toStdWString();
|
||||
}
|
||||
#else
|
||||
using tstring = std::string;
|
||||
static inline tstring toTString(QString s) { return s.toStdString(); }
|
||||
static inline tstring toTString(QString s)
|
||||
{
|
||||
return s.toStdString();
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Platform
|
||||
namespace Platform {
|
||||
inline tstring currentCommandLine()
|
||||
{
|
||||
inline tstring currentCommandLine()
|
||||
{
|
||||
return toTString("\"" + QApplication::applicationFilePath().replace('/', '\\') + "\" -p \"" +
|
||||
Settings::getInstance().getCurrentProfile() + "\"");
|
||||
}
|
||||
return toTString("\"" + QApplication::applicationFilePath().replace('/', '\\') + "\" -p \""
|
||||
+ Settings::getInstance().getCurrentProfile() + "\"");
|
||||
}
|
||||
|
||||
inline tstring currentRegistryKeyName()
|
||||
{
|
||||
return toTString("qTox - " + Settings::getInstance().getCurrentProfile());
|
||||
}
|
||||
inline tstring currentRegistryKeyName()
|
||||
{
|
||||
return toTString("qTox - " + Settings::getInstance().getCurrentProfile());
|
||||
}
|
||||
}
|
||||
|
||||
bool Platform::setAutorun(bool on)
|
||||
{
|
||||
HKEY key = 0;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"),
|
||||
0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS)
|
||||
0, KEY_ALL_ACCESS, &key)
|
||||
!= ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
bool result = false;
|
||||
tstring keyName = currentRegistryKeyName();
|
||||
|
||||
if (on)
|
||||
{
|
||||
if (on) {
|
||||
tstring path = currentCommandLine();
|
||||
result = RegSetValueEx(key, keyName.c_str(), 0, REG_SZ, (PBYTE)path.c_str(),
|
||||
path.length() * sizeof(TCHAR)) == ERROR_SUCCESS;
|
||||
}
|
||||
else
|
||||
path.length() * sizeof(TCHAR))
|
||||
== ERROR_SUCCESS;
|
||||
} else
|
||||
result = RegDeleteValue(key, keyName.c_str()) == ERROR_SUCCESS;
|
||||
|
||||
RegCloseKey(key);
|
||||
@ -78,21 +83,23 @@ bool Platform::getAutorun()
|
||||
{
|
||||
HKEY key = 0;
|
||||
if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"),
|
||||
0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS)
|
||||
0, KEY_ALL_ACCESS, &key)
|
||||
!= ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
tstring keyName = currentRegistryKeyName();
|
||||
|
||||
TCHAR path[MAX_PATH] = { 0 };
|
||||
TCHAR path[MAX_PATH] = {0};
|
||||
DWORD length = sizeof(path);
|
||||
DWORD type = REG_SZ;
|
||||
bool result = false;
|
||||
|
||||
if (RegQueryValueEx(key, keyName.c_str(), 0, &type, (PBYTE)path, &length) == ERROR_SUCCESS && type == REG_SZ)
|
||||
if (RegQueryValueEx(key, keyName.c_str(), 0, &type, (PBYTE)path, &length) == ERROR_SUCCESS
|
||||
&& type == REG_SZ)
|
||||
result = true;
|
||||
|
||||
RegCloseKey(key);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // Q_OS_WIN32
|
||||
#endif // Q_OS_WIN32
|
||||
|
@ -19,43 +19,39 @@
|
||||
|
||||
#include <QApplication>
|
||||
#if defined(Q_OS_UNIX) && !defined(__APPLE__) && !defined(__MACH__)
|
||||
#include "src/platform/autorun.h"
|
||||
#include "src/persistence/settings.h"
|
||||
#include <QProcessEnvironment>
|
||||
#include "src/platform/autorun.h"
|
||||
#include <QDir>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
namespace Platform
|
||||
namespace Platform {
|
||||
QString getAutostartDirPath()
|
||||
{
|
||||
QString getAutostartDirPath()
|
||||
{
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
QString config = env.value("XDG_CONFIG_HOME");
|
||||
if (config.isEmpty())
|
||||
config = QDir::homePath() + "/" + ".config";
|
||||
return config + "/autostart";
|
||||
}
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
QString config = env.value("XDG_CONFIG_HOME");
|
||||
if (config.isEmpty())
|
||||
config = QDir::homePath() + "/" + ".config";
|
||||
return config + "/autostart";
|
||||
}
|
||||
|
||||
QString getAutostartFilePath(QString dir)
|
||||
{
|
||||
return dir + "/qTox - " + Settings::getInstance().getCurrentProfile() +
|
||||
".desktop";
|
||||
}
|
||||
QString getAutostartFilePath(QString dir)
|
||||
{
|
||||
return dir + "/qTox - " + Settings::getInstance().getCurrentProfile() + ".desktop";
|
||||
}
|
||||
|
||||
inline QString currentCommandLine()
|
||||
{
|
||||
return "\"" + QApplication::applicationFilePath() + "\" -p \"" +
|
||||
Settings::getInstance().getCurrentProfile() + "\"";
|
||||
}
|
||||
inline QString currentCommandLine()
|
||||
{
|
||||
return "\"" + QApplication::applicationFilePath() + "\" -p \""
|
||||
+ Settings::getInstance().getCurrentProfile() + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
bool Platform::setAutorun(bool on)
|
||||
{
|
||||
QString dirPath = getAutostartDirPath();
|
||||
QFile desktop(getAutostartFilePath(dirPath));
|
||||
if (on)
|
||||
{
|
||||
if (!QDir().mkpath(dirPath) ||
|
||||
!desktop.open(QFile::WriteOnly | QFile::Truncate))
|
||||
if (on) {
|
||||
if (!QDir().mkpath(dirPath) || !desktop.open(QFile::WriteOnly | QFile::Truncate))
|
||||
return false;
|
||||
desktop.write("[Desktop Entry]\n");
|
||||
desktop.write("Type=Application\n");
|
||||
@ -65,8 +61,7 @@ bool Platform::setAutorun(bool on)
|
||||
desktop.write("\n");
|
||||
desktop.close();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
} else
|
||||
return desktop.remove();
|
||||
}
|
||||
|
||||
@ -75,4 +70,4 @@ bool Platform::getAutorun()
|
||||
return QFile(getAutostartFilePath(getAutostartDirPath())).exists();
|
||||
}
|
||||
|
||||
#endif // defined(Q_OS_UNIX) && !defined(__APPLE__) && !defined(__MACH__)
|
||||
#endif // defined(Q_OS_UNIX) && !defined(__APPLE__) && !defined(__MACH__)
|
||||
|
@ -19,21 +19,20 @@
|
||||
#ifndef AVFOUNDATION_H
|
||||
#define AVFOUNDATION_H
|
||||
|
||||
#include "src/video/videomode.h"
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#include "src/video/videomode.h"
|
||||
|
||||
#ifndef Q_OS_MACX
|
||||
#error "This file is only meant to be compiled for Mac OS X targets"
|
||||
#endif
|
||||
|
||||
namespace avfoundation
|
||||
{
|
||||
const QString CAPTURE_SCREEN{"Capture screen"};
|
||||
namespace avfoundation {
|
||||
const QString CAPTURE_SCREEN{"Capture screen"};
|
||||
|
||||
QVector<VideoMode> getDeviceModes(QString devName);
|
||||
QVector<QPair<QString, QString>> getDeviceList();
|
||||
QVector<VideoMode> getDeviceModes(QString devName);
|
||||
QVector<QPair<QString, QString>> getDeviceList();
|
||||
}
|
||||
|
||||
#endif // AVFOUNDATION_H
|
||||
|
@ -21,14 +21,14 @@
|
||||
|
||||
#include "directshow.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <amvideo.h>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <dvdmedia.h>
|
||||
#include <objbase.h>
|
||||
#include <strmif.h>
|
||||
#include <amvideo.h>
|
||||
#include <dvdmedia.h>
|
||||
#include <uuids.h>
|
||||
#include <cassert>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
* Most of this file is adapted from libavdevice's dshow.c,
|
||||
@ -36,38 +36,38 @@
|
||||
* stdout and is not part of the public API for some reason.
|
||||
*/
|
||||
|
||||
static char *wcharToUtf8(wchar_t *w)
|
||||
static char* wcharToUtf8(wchar_t* w)
|
||||
{
|
||||
int l = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0);
|
||||
char *s = new char[l];
|
||||
char* s = new char[l];
|
||||
if (s)
|
||||
WideCharToMultiByte(CP_UTF8, 0, w, -1, s, l, 0, 0);
|
||||
return s;
|
||||
}
|
||||
|
||||
QVector<QPair<QString,QString>> DirectShow::getDeviceList()
|
||||
QVector<QPair<QString, QString>> DirectShow::getDeviceList()
|
||||
{
|
||||
IMoniker* m = nullptr;
|
||||
QVector<QPair<QString,QString>> devices;
|
||||
QVector<QPair<QString, QString>> devices;
|
||||
|
||||
ICreateDevEnum* devenum = nullptr;
|
||||
if (CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_ICreateDevEnum, (void**) &devenum) != S_OK)
|
||||
if (CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
|
||||
(void**)&devenum)
|
||||
!= S_OK)
|
||||
return devices;
|
||||
|
||||
IEnumMoniker* classenum = nullptr;
|
||||
if (devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
|
||||
(IEnumMoniker**)&classenum, 0) != S_OK)
|
||||
if (devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, (IEnumMoniker**)&classenum, 0)
|
||||
!= S_OK)
|
||||
return devices;
|
||||
|
||||
while (classenum->Next(1, &m, nullptr) == S_OK)
|
||||
{
|
||||
while (classenum->Next(1, &m, nullptr) == S_OK) {
|
||||
VARIANT var;
|
||||
IPropertyBag* bag = nullptr;
|
||||
LPMALLOC coMalloc = nullptr;
|
||||
IBindCtx* bindCtx = nullptr;
|
||||
LPOLESTR olestr = nullptr;
|
||||
char *devIdString=nullptr, *devHumanName=nullptr;
|
||||
char *devIdString = nullptr, *devHumanName = nullptr;
|
||||
|
||||
if (CoGetMalloc(1, &coMalloc) != S_OK)
|
||||
goto fail;
|
||||
@ -93,9 +93,9 @@ QVector<QPair<QString,QString>> DirectShow::getDeviceList()
|
||||
goto fail;
|
||||
devHumanName = wcharToUtf8(var.bstrVal);
|
||||
|
||||
devices += {QString("video=")+devIdString, devHumanName};
|
||||
devices += {QString("video=") + devIdString, devHumanName};
|
||||
|
||||
fail:
|
||||
fail:
|
||||
if (olestr && coMalloc)
|
||||
coMalloc->Free(olestr);
|
||||
if (bindCtx)
|
||||
@ -120,17 +120,17 @@ static IBaseFilter* getDevFilter(QString devName)
|
||||
IMoniker* m = nullptr;
|
||||
|
||||
ICreateDevEnum* devenum = nullptr;
|
||||
if (CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_ICreateDevEnum, (void**) &devenum) != S_OK)
|
||||
if (CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
|
||||
(void**)&devenum)
|
||||
!= S_OK)
|
||||
return devFilter;
|
||||
|
||||
IEnumMoniker* classenum = nullptr;
|
||||
if (devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
|
||||
(IEnumMoniker**)&classenum, 0) != S_OK)
|
||||
if (devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, (IEnumMoniker**)&classenum, 0)
|
||||
!= S_OK)
|
||||
return devFilter;
|
||||
|
||||
while (classenum->Next(1, &m, nullptr) == S_OK)
|
||||
{
|
||||
while (classenum->Next(1, &m, nullptr) == S_OK) {
|
||||
LPMALLOC coMalloc = nullptr;
|
||||
IBindCtx* bindCtx = nullptr;
|
||||
LPOLESTR olestr = nullptr;
|
||||
@ -156,7 +156,7 @@ static IBaseFilter* getDevFilter(QString devName)
|
||||
if (m->BindToObject(0, 0, IID_IBaseFilter, (void**)&devFilter) != S_OK)
|
||||
goto fail;
|
||||
|
||||
fail:
|
||||
fail:
|
||||
if (olestr && coMalloc)
|
||||
coMalloc->Free(olestr);
|
||||
if (bindCtx)
|
||||
@ -167,7 +167,7 @@ fail:
|
||||
classenum->Release();
|
||||
|
||||
if (!devFilter)
|
||||
qWarning() << "Could't find the device "<<devName;
|
||||
qWarning() << "Could't find the device " << devName;
|
||||
|
||||
return devFilter;
|
||||
}
|
||||
@ -183,14 +183,13 @@ QVector<VideoMode> DirectShow::getDeviceModes(QString devName)
|
||||
// The outter loop tries to find a valid output pin
|
||||
GUID category;
|
||||
DWORD r2;
|
||||
IEnumPins *pins = nullptr;
|
||||
IPin *pin;
|
||||
IEnumPins* pins = nullptr;
|
||||
IPin* pin;
|
||||
if (devFilter->EnumPins(&pins) != S_OK)
|
||||
return modes;
|
||||
|
||||
while (pins->Next(1, &pin, nullptr) == S_OK)
|
||||
{
|
||||
IKsPropertySet *p = nullptr;
|
||||
while (pins->Next(1, &pin, nullptr) == S_OK) {
|
||||
IKsPropertySet* p = nullptr;
|
||||
PIN_INFO info;
|
||||
|
||||
pin->QueryPinInfo(&info);
|
||||
@ -199,8 +198,8 @@ QVector<VideoMode> DirectShow::getDeviceModes(QString devName)
|
||||
goto next;
|
||||
if (pin->QueryInterface(IID_IKsPropertySet, (void**)&p) != S_OK)
|
||||
goto next;
|
||||
if (p->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY,
|
||||
nullptr, 0, &category, sizeof(GUID), &r2) != S_OK)
|
||||
if (p->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, nullptr, 0, &category, sizeof(GUID), &r2)
|
||||
!= S_OK)
|
||||
goto next;
|
||||
if (!IsEqualGUID(category, PIN_CATEGORY_CAPTURE))
|
||||
goto next;
|
||||
@ -208,8 +207,8 @@ QVector<VideoMode> DirectShow::getDeviceModes(QString devName)
|
||||
// Now we can list the video modes for the current pin
|
||||
// Prepare for another wall of spaghetti DIRECT SHOW QUALITY code
|
||||
{
|
||||
IAMStreamConfig *config = nullptr;
|
||||
VIDEO_STREAM_CONFIG_CAPS *vcaps = nullptr;
|
||||
IAMStreamConfig* config = nullptr;
|
||||
VIDEO_STREAM_CONFIG_CAPS* vcaps = nullptr;
|
||||
int size, n;
|
||||
if (pin->QueryInterface(IID_IAMStreamConfig, (void**)&config) != S_OK)
|
||||
goto next;
|
||||
@ -219,8 +218,7 @@ QVector<VideoMode> DirectShow::getDeviceModes(QString devName)
|
||||
assert(size == sizeof(VIDEO_STREAM_CONFIG_CAPS));
|
||||
vcaps = new VIDEO_STREAM_CONFIG_CAPS;
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
for (int i = 0; i < n; ++i) {
|
||||
AM_MEDIA_TYPE* type = nullptr;
|
||||
VideoMode mode;
|
||||
if (config->GetStreamCaps(i, &type, (BYTE*)vcaps) != S_OK)
|
||||
@ -236,16 +234,16 @@ QVector<VideoMode> DirectShow::getDeviceModes(QString devName)
|
||||
if (!modes.contains(mode))
|
||||
modes.append(std::move(mode));
|
||||
|
||||
nextformat:
|
||||
nextformat:
|
||||
if (type->pbFormat)
|
||||
CoTaskMemFree(type->pbFormat);
|
||||
CoTaskMemFree(type);
|
||||
}
|
||||
pinend:
|
||||
pinend:
|
||||
config->Release();
|
||||
delete vcaps;
|
||||
}
|
||||
next:
|
||||
next:
|
||||
if (p)
|
||||
p->Release();
|
||||
pin->Release();
|
||||
|
@ -21,19 +21,18 @@
|
||||
#ifndef DIRECTSHOW_H
|
||||
#define DIRECTSHOW_H
|
||||
|
||||
#include "src/video/videomode.h"
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#include "src/video/videomode.h"
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#error "This file is only meant to be compiled for Windows targets"
|
||||
#endif
|
||||
|
||||
namespace DirectShow
|
||||
{
|
||||
QVector<QPair<QString,QString>> getDeviceList();
|
||||
QVector<VideoMode> getDeviceModes(QString devName);
|
||||
namespace DirectShow {
|
||||
QVector<QPair<QString, QString>> getDeviceList();
|
||||
QVector<VideoMode> getDeviceModes(QString devName);
|
||||
}
|
||||
|
||||
#endif // DIRECTSHOW_H
|
||||
|
@ -22,14 +22,14 @@
|
||||
|
||||
#include "v4l2.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <dirent.h>
|
||||
#include <map>
|
||||
#include <QDebug>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <map>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Most of this file is adapted from libavdevice's v4l2.c,
|
||||
@ -37,25 +37,25 @@
|
||||
* stdout and is not part of the public API for some reason.
|
||||
*/
|
||||
|
||||
static std::map<uint32_t,uint8_t> createPixFmtToQuality()
|
||||
static std::map<uint32_t, uint8_t> createPixFmtToQuality()
|
||||
{
|
||||
std::map<uint32_t,uint8_t> m;
|
||||
std::map<uint32_t, uint8_t> m;
|
||||
m[V4L2_PIX_FMT_H264] = 3;
|
||||
m[V4L2_PIX_FMT_MJPEG] = 2;
|
||||
m[V4L2_PIX_FMT_YUYV] = 1;
|
||||
return m;
|
||||
}
|
||||
const std::map<uint32_t,uint8_t> pixFmtToQuality = createPixFmtToQuality();
|
||||
const std::map<uint32_t, uint8_t> pixFmtToQuality = createPixFmtToQuality();
|
||||
|
||||
static std::map<uint32_t,QString> createPixFmtToName()
|
||||
static std::map<uint32_t, QString> createPixFmtToName()
|
||||
{
|
||||
std::map<uint32_t,QString> m;
|
||||
std::map<uint32_t, QString> m;
|
||||
m[V4L2_PIX_FMT_H264] = QString("h264");
|
||||
m[V4L2_PIX_FMT_MJPEG] = QString("mjpeg");
|
||||
m[V4L2_PIX_FMT_YUYV] = QString("yuyv422");
|
||||
return m;
|
||||
}
|
||||
const std::map<uint32_t,QString> pixFmtToName = createPixFmtToName();
|
||||
const std::map<uint32_t, QString> pixFmtToName = createPixFmtToName();
|
||||
|
||||
static int deviceOpen(QString devName, int* error)
|
||||
{
|
||||
@ -90,7 +90,8 @@ fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static QVector<unsigned short> getDeviceModeFramerates(int fd, unsigned w, unsigned h, uint32_t pixelFormat)
|
||||
static QVector<unsigned short> getDeviceModeFramerates(int fd, unsigned w, unsigned h,
|
||||
uint32_t pixelFormat)
|
||||
{
|
||||
QVector<unsigned short> rates;
|
||||
v4l2_frmivalenum vfve{};
|
||||
@ -105,7 +106,7 @@ static QVector<unsigned short> getDeviceModeFramerates(int fd, unsigned w, unsig
|
||||
rate = vfve.discrete.denominator / vfve.discrete.numerator;
|
||||
if (!rates.contains(rate))
|
||||
rates.append(rate);
|
||||
break;
|
||||
break;
|
||||
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
|
||||
case V4L2_FRMSIZE_TYPE_STEPWISE:
|
||||
rate = vfve.stepwise.min.denominator / vfve.stepwise.min.numerator;
|
||||
@ -129,34 +130,32 @@ QVector<VideoMode> v4l2::getDeviceModes(QString devName)
|
||||
v4l2_fmtdesc vfd{};
|
||||
vfd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
while (!ioctl(fd, VIDIOC_ENUM_FMT, &vfd))
|
||||
{
|
||||
while (!ioctl(fd, VIDIOC_ENUM_FMT, &vfd)) {
|
||||
vfd.index++;
|
||||
|
||||
v4l2_frmsizeenum vfse{};
|
||||
vfse.pixel_format = vfd.pixelformat;
|
||||
|
||||
while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &vfse))
|
||||
{
|
||||
while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &vfse)) {
|
||||
VideoMode mode;
|
||||
mode.pixel_format = vfse.pixel_format;
|
||||
switch (vfse.type) {
|
||||
case V4L2_FRMSIZE_TYPE_DISCRETE:
|
||||
mode.width = vfse.discrete.width;
|
||||
mode.height = vfse.discrete.height;
|
||||
break;
|
||||
break;
|
||||
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
|
||||
case V4L2_FRMSIZE_TYPE_STEPWISE:
|
||||
mode.width = vfse.stepwise.max_width;
|
||||
mode.height = vfse.stepwise.max_height;
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<unsigned short> rates = getDeviceModeFramerates(fd, mode.width, mode.height, vfd.pixelformat);
|
||||
for (unsigned short rate : rates)
|
||||
{
|
||||
QVector<unsigned short> rates =
|
||||
getDeviceModeFramerates(fd, mode.width, mode.height, vfd.pixelformat);
|
||||
for (unsigned short rate : rates) {
|
||||
mode.FPS = rate;
|
||||
if (!modes.contains(mode))
|
||||
modes.append(std::move(mode));
|
||||
@ -173,7 +172,7 @@ QVector<QPair<QString, QString>> v4l2::getDeviceList()
|
||||
QVector<QPair<QString, QString>> devices;
|
||||
QVector<QString> deviceFiles;
|
||||
|
||||
DIR *dir = opendir("/dev");
|
||||
DIR* dir = opendir("/dev");
|
||||
if (!dir)
|
||||
return devices;
|
||||
|
||||
@ -183,8 +182,7 @@ QVector<QPair<QString, QString>> v4l2::getDeviceList()
|
||||
deviceFiles += QString("/dev/") + e->d_name;
|
||||
closedir(dir);
|
||||
|
||||
for (QString file : deviceFiles)
|
||||
{
|
||||
for (QString file : deviceFiles) {
|
||||
int fd = open(file.toStdString().c_str(), O_RDWR);
|
||||
if (fd < 0)
|
||||
continue;
|
||||
@ -200,8 +198,7 @@ QVector<QPair<QString, QString>> v4l2::getDeviceList()
|
||||
|
||||
QString v4l2::getPixelFormatString(uint32_t pixel_format)
|
||||
{
|
||||
if (pixFmtToName.find(pixel_format) == pixFmtToName.end())
|
||||
{
|
||||
if (pixFmtToName.find(pixel_format) == pixFmtToName.end()) {
|
||||
qWarning() << "Pixel format not found";
|
||||
return QString("unknown");
|
||||
}
|
||||
@ -210,14 +207,10 @@ QString v4l2::getPixelFormatString(uint32_t pixel_format)
|
||||
|
||||
bool v4l2::betterPixelFormat(uint32_t a, uint32_t b)
|
||||
{
|
||||
if (pixFmtToQuality.find(a) == pixFmtToQuality.end())
|
||||
{
|
||||
if (pixFmtToQuality.find(a) == pixFmtToQuality.end()) {
|
||||
return false;
|
||||
}
|
||||
else if (pixFmtToQuality.find(b) == pixFmtToQuality.end())
|
||||
{
|
||||
} else if (pixFmtToQuality.find(b) == pixFmtToQuality.end()) {
|
||||
return true;
|
||||
}
|
||||
return pixFmtToQuality.at(a) > pixFmtToQuality.at(b);
|
||||
return pixFmtToQuality.at(a) > pixFmtToQuality.at(b);
|
||||
}
|
||||
|
||||
|
@ -19,22 +19,20 @@
|
||||
#ifndef V4L2_H
|
||||
#define V4L2_H
|
||||
|
||||
#include "src/video/videomode.h"
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#include "src/video/videomode.h"
|
||||
|
||||
#if !(defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD))
|
||||
#error "This file is only meant to be compiled for Linux or FreeBSD targets"
|
||||
#endif
|
||||
|
||||
namespace v4l2
|
||||
{
|
||||
QVector<VideoMode> getDeviceModes(QString devName);
|
||||
QVector<QPair<QString, QString>> getDeviceList();
|
||||
QString getPixelFormatString(uint32_t pixel_format);
|
||||
bool betterPixelFormat(uint32_t a, uint32_t b);
|
||||
namespace v4l2 {
|
||||
QVector<VideoMode> getDeviceModes(QString devName);
|
||||
QVector<QPair<QString, QString>> getDeviceList();
|
||||
QString getPixelFormatString(uint32_t pixel_format);
|
||||
bool betterPixelFormat(uint32_t a, uint32_t b);
|
||||
}
|
||||
|
||||
#endif // V4L2_H
|
||||
|
||||
|
@ -23,9 +23,8 @@
|
||||
#define PLATFORM_CAPSLOCK_H
|
||||
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
bool capsLockEnabled();
|
||||
namespace Platform {
|
||||
bool capsLockEnabled();
|
||||
}
|
||||
|
||||
#endif // PLATFORM_CAPSLOCK_H
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user