1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge pull request #3536

Diadlo (9):
      refactor(videomode): Move implementation in cpp file
      docs(audio, video): Change comment style
      docs(chatlog): Change comment style
      docs(net): Change comment style
      docs(widget): Change comment style
      docs(persistence): Change comment style
      docs(core): Change comment style
      feat(doxygen): Created simple doxygen config file
      docs(CONTRIBUTING.md): Added documntation block
This commit is contained in:
sudden6 2016-07-28 21:27:31 +02:00
commit f61fbfec14
No known key found for this signature in database
GPG Key ID: 279509B499E032B9
109 changed files with 2186 additions and 710 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ build-*-Release
build-*-Profile
build-*-Debug
.DS_Store
doc/html/*

View File

@ -197,6 +197,57 @@ QObject notToMentionThatWeUseCamelCase;
E.g. https://github.com/tux3/qTox/blob/master/src/misc/flowlayout.cpp
## Documentaion
If you added a new function, also add a doxygen comment before the implementation.
If you changed an old function, make sure the doxygen comment is still correct.
If it doesn't exist add it.
Don't put docs in .h files, if there is a corresponding .cpp file.
### Documentation style
```C++
/*...license info...*/
#include "blabla.h"
/**
I can be briefly described as well!
*/
static void method()
{
// I'm just a little example.
}
/**
@class OurClass
@brief Exists for some reason...!?
Longer description
*/
/**
@enum OurClass::OurEnum
@brief The brief description line.
@var EnumValue1
means something
@var EnumValue2
means something else
Optional long description
*/
/**
@fn OurClass::somethingHappened(const QString &happened)
@param[in] happened tells what has happened...
@brief This signal is emitted when something has happened in the class.
Here's an optional longer description of what the signal additionally does.
*/
```
## No translatable HTML tags
Do not put HTML in UI files, or inside `tr()`. Instead, you can put put it in

20
doxygen.conf Normal file
View File

@ -0,0 +1,20 @@
# General
PROJECT_NAME = qTox
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
SOURCE_BROWSER = YES
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
# Input
INPUT = src
FILE_PATTERNS = *.h *.cpp
RECURSIVE = YES
# Output
OUTPUT_DIRECTORY = doc
OUTPUT_LANGUAGE = English
GENERATE_LATEX = NO
GENERATE_HTML = YES
HTML_OUTPUT = html

View File

@ -472,4 +472,5 @@ SOURCES += \
src/widget/about/aboutuser.cpp \
src/widget/form/groupinviteform.cpp \
src/widget/tool/profileimporter.cpp \
src/widget/passwordedit.cpp
src/widget/passwordedit.cpp \
src/video/videomode.cpp

View File

@ -33,8 +33,6 @@
#include <cassert>
/**
@internal
@class Audio::Private
@brief Encapsulates private audio framework from public qTox Audio API.
@ -88,7 +86,28 @@ private:
};
/**
Returns the singleton instance.
@class Audio
@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
@var Audio::AUDIO_SAMPLE_RATE
@brief The next best Opus would take is 24k
@var Audio::AUDIO_FRAME_DURATION
@brief In milliseconds
@var Audio::AUDIO_FRAME_SAMPLE_COUNT
@brief Frame sample count
@var Audio::AUDIO_CHANNELS
@brief Ideally, we'd auto-detect, but that's a sane default
*/
/**
@brief Returns the singleton instance.
*/
Audio& Audio::getInstance()
{
@ -150,7 +169,7 @@ void Audio::checkAlcError(ALCdevice *device) noexcept
}
/**
Returns the current output volume (between 0 and 1)
@brief Returns the current output volume (between 0 and 1)
*/
qreal Audio::outputVolume() const
{
@ -168,7 +187,7 @@ qreal Audio::outputVolume() const
}
/**
Set the master output volume.
@brief Set the master output volume.
@param[in] volume the master volume (between 0 and 1)
*/
@ -238,7 +257,7 @@ qreal Audio::inputGain() const
}
/**
Set the input gain dB level.
@brief Set the input gain dB level.
*/
void Audio::setInputGain(qreal dB)
{
@ -299,7 +318,7 @@ void Audio::unsubscribeInput()
}
/**
Initialize audio input device, if not initialized.
@brief Initialize audio input device, if not initialized.
@return true, if device was initialized; false otherwise
*/
@ -309,7 +328,7 @@ bool Audio::autoInitInput()
}
/**
Initialize audio output device, if not initialized.
@brief Initialize audio output device, if not initialized.
@return true, if device was initialized; false otherwise
*/
@ -354,9 +373,7 @@ bool Audio::initInput(const QString& deviceName)
}
/**
@internal
Open an audio output device
@brief Open an audio output device
*/
bool Audio::initOutput(const QString& deviceName)
{
@ -409,7 +426,7 @@ bool Audio::initOutput(const QString& deviceName)
}
/**
Play a 44100Hz mono 16bit PCM sound from a file
@brief Play a 44100Hz mono 16bit PCM sound from a file
*/
void Audio::playMono16Sound(const QString& path)
{
@ -419,7 +436,7 @@ void Audio::playMono16Sound(const QString& path)
}
/**
Play a 44100Hz mono 16bit PCM sound
@brief Play a 44100Hz mono 16bit PCM sound
*/
void Audio::playMono16Sound(const QByteArray& data)
{
@ -488,9 +505,7 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u
}
/**
@internal
Close active audio input device.
@brief Close active audio input device.
*/
void Audio::cleanupInput()
{
@ -506,9 +521,7 @@ void Audio::cleanupInput()
}
/**
@internal
Close active audio output device
@brief Close active audio output device
*/
void Audio::cleanupOutput()
{
@ -540,6 +553,9 @@ void Audio::cleanupOutput()
}
}
/**
@brief Called after a mono16 sound stopped playing
*/
void Audio::playMono16SoundCleanup()
{
QMutexLocker locker(&audioLock);
@ -554,6 +570,9 @@ void Audio::playMono16SoundCleanup()
}
}
/**
@brief Called on the captureTimer events to capture audio
*/
void Audio::doCapture()
{
QMutexLocker lock(&audioLock);
@ -583,7 +602,7 @@ void Audio::doCapture()
}
/**
Returns true if the output device is open
@brief Returns true if the output device is open
*/
bool Audio::isOutputReady() const
{

View File

@ -42,13 +42,6 @@
#include <AL/alext.h>
#endif
// Public default audio settings
static constexpr uint32_t AUDIO_SAMPLE_RATE = 48000; ///< The next best Opus would take is 24k
static constexpr uint32_t AUDIO_FRAME_DURATION = 20; ///< In milliseconds
static constexpr ALint AUDIO_FRAME_SAMPLE_COUNT = AUDIO_FRAME_DURATION * AUDIO_SAMPLE_RATE/1000;
static constexpr uint32_t AUDIO_CHANNELS = 2; ///< Ideally, we'd auto-detect, but that's a sane default
class Audio : public QObject
{
Q_OBJECT
@ -92,10 +85,15 @@ public:
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 uint32_t AUDIO_CHANNELS = 2;
signals:
void groupAudioPlayed(int group, int peer, unsigned short volume);
/// When there are input subscribers, we regularly emit captured audio frames with this signal
/// Always connect with a blocking queued connection or a lambda, or the behavior is undefined
void frameAvailable(const int16_t *pcm, size_t sample_count, uint8_t channels, uint32_t sampling_rate);
private:
@ -111,12 +109,9 @@ private:
bool initOutput(const QString& outDevDescr);
void cleanupInput();
void cleanupOutput();
/// Called after a mono16 sound stopped playing
void playMono16SoundCleanup();
/// Called on the captureTimer events to capture audio
void doCapture();
private:
Private* d;

View File

@ -24,6 +24,14 @@
#include <QPainter>
#include <QDebug>
/**
@enum ChatLineContentProxy::ChatLineContentProxyType
@brief Type tag to avoid dynamic_cast of contained QWidget*
@value GenericType
@value FileTransferWidgetType = 0
*/
ChatLineContentProxy::ChatLineContentProxy(QWidget* widget, ChatLineContentProxyType type, int minWidth, float widthInPercent)
: widthPercent(widthInPercent)
, widthMin(minWidth)

View File

@ -28,7 +28,6 @@ class FileTransferWidget;
class ChatLineContentProxy : public ChatLineContent
{
public:
// Type tag to avoid dynamic_cast of contained QWidget*
enum ChatLineContentProxyType
{
GenericType,

View File

@ -31,6 +31,11 @@
#include <QMouseEvent>
#include <QShortcut>
/**
@var ChatLog::repNameAfter
@brief repetition interval sender name (sec)
*/
template<class T>
T clamp(T x, T min, T max)
{

View File

@ -62,7 +62,6 @@ public:
ChatLine::Ptr getTypingNotification() const;
QVector<ChatLine::Ptr> getLines();
ChatLine::Ptr getLatestLine() const;
// repetition interval sender name (sec)
const uint repNameAfter = 5*60;
signals:

View File

@ -43,6 +43,9 @@ void DocumentCache::push(QTextDocument *doc)
}
}
/**
@brief Returns the singleton instance.
*/
DocumentCache &DocumentCache::getInstance()
{
static DocumentCache instance;

View File

@ -35,6 +35,9 @@ QPixmap PixmapCache::get(const QString &filename, QSize size)
return itr.value().pixmap(size);
}
/**
@brief Returns the singleton instance.
*/
PixmapCache &PixmapCache::getInstance()
{
static PixmapCache instance;

View File

@ -104,6 +104,9 @@ Core::~Core()
deadifyTox();
}
/**
@brief Returns the global widget's Core instance
*/
Core* Core::getInstance()
{
return Nexus::getCore();
@ -217,6 +220,9 @@ void Core::makeTox(QByteArray savedata)
}
}
/**
@brief Initializes the core, must be called before anything else
*/
void Core::start()
{
bool isNewProfile = profile.isNewProfile();
@ -329,6 +335,9 @@ void Core::start()
*/
#define CORE_DISCONNECT_TOLERANCE 30
/**
@brief Processes toxcore events and ensure we stay connected, called by its own timer
*/
void Core::process()
{
if (!isReady())
@ -383,6 +392,9 @@ bool Core::checkConnection()
return isConnected;
}
/**
@brief Connects us to the Tox network
*/
void Core::bootstrapDht()
{
const Settings& s = Settings::getInstance();
@ -720,6 +732,9 @@ void Core::removeGroup(int groupId, bool fake)
av->leaveGroupCall(groupId);
}
/**
@brief Returns our username, or an empty string on failure
*/
QString Core::getUsername() const
{
QString sname;
@ -769,6 +784,9 @@ void Core::setAvatar(const QByteArray& data)
AvatarBroadcaster::enableAutoBroadcast();
}
/**
@brief Returns our Tox ID
*/
ToxId Core::getSelfId() const
{
uint8_t friendAddress[TOX_ADDRESS_SIZE] = {0};
@ -776,6 +794,9 @@ ToxId Core::getSelfId() const
return ToxId(CFriendAddress::toString(friendAddress));
}
/**
@brief Returns our public and private keys
*/
QPair<QByteArray, QByteArray> Core::getKeypair() const
{
QPair<QByteArray, QByteArray> keypair;
@ -790,6 +811,9 @@ QPair<QByteArray, QByteArray> Core::getKeypair() const
return keypair;
}
/**
@brief Returns our status message, or an empty string on failure
*/
QString Core::getStatusMessage() const
{
QString sname;
@ -803,6 +827,9 @@ QString Core::getStatusMessage() const
return sname;
}
/**
@brief Returns our user status
*/
Status Core::getStatus() const
{
return (Status)tox_self_get_status(tox);
@ -867,6 +894,9 @@ QString Core::sanitize(QString name)
return name;
}
/**
@brief Returns the unencrypted tox save data
*/
QByteArray Core::getToxSaveData()
{
uint32_t fileSize = tox_get_savedata_size(tox);
@ -925,6 +955,9 @@ void Core::checkLastOnline(uint32_t friendId) {
emit friendLastSeenChanged(friendId, QDateTime::fromTime_t(lastOnline));
}
/**
@brief Returns the list of friendIds in our friendlist, an empty list on error
*/
QVector<uint32_t> Core::getFriendList() const
{
QVector<uint32_t> friends;
@ -933,11 +966,17 @@ QVector<uint32_t> Core::getFriendList() const
return friends;
}
/**
@brief Return the number of peers in the group chat on success, or -1 on failure
*/
int Core::getGroupNumberPeers(int groupId) const
{
return tox_group_number_peers(tox, groupId);
}
/**
@brief Get the name of a peer of a group
*/
QString Core::getGroupPeerName(int groupId, int peerId) const
{
QString name;
@ -952,6 +991,9 @@ QString Core::getGroupPeerName(int groupId, int peerId) const
return name;
}
/**
@brief Get the public key of a peer of a group
*/
ToxId Core::getGroupPeerToxId(int groupId, int peerId) const
{
ToxId peerToxId;
@ -968,6 +1010,9 @@ ToxId Core::getGroupPeerToxId(int groupId, int peerId) const
return peerToxId;
}
/**
@brief Get the names of the peers of a group
*/
QList<QString> Core::getGroupPeerNames(int groupId) const
{
QList<QString> names;
@ -999,6 +1044,9 @@ QList<QString> Core::getGroupPeerNames(int groupId) const
return names;
}
/**
@brief Accept a groupchat invite
*/
int Core::joinGroupchat(int32_t friendnumber, uint8_t type, const uint8_t* friend_group_public_key,uint16_t length) const
{
if (type == TOX_GROUPCHAT_TYPE_TEXT)
@ -1020,6 +1068,9 @@ int Core::joinGroupchat(int32_t friendnumber, uint8_t type, const uint8_t* frien
}
}
/**
@brief Quit a groupchat
*/
void Core::quitGroupChat(int groupId) const
{
tox_del_groupchat(tox, groupId);
@ -1052,11 +1103,17 @@ int Core::createGroup(uint8_t type)
}
}
/**
@brief Checks if a friend is online. Unknown friends are considered offline.
*/
bool Core::isFriendOnline(uint32_t friendId) const
{
return tox_friend_get_connection_status(tox, friendId, nullptr) != TOX_CONNECTION_NONE;
}
/**
@brief Checks if we have a friend by address
*/
bool Core::hasFriendWithAddress(const QString &addr) const
{
// Valid length check
@ -1069,6 +1126,9 @@ bool Core::hasFriendWithAddress(const QString &addr) const
return hasFriendWithPublicKey(pubkey);
}
/**
@brief Checks if we have a friend by public key
*/
bool Core::hasFriendWithPublicKey(const QString &pubkey) const
{
// Valid length check
@ -1100,6 +1160,9 @@ bool Core::hasFriendWithPublicKey(const QString &pubkey) const
return found;
}
/**
@brief Get the full address if known, or public key of a friend
*/
QString Core::getFriendAddress(uint32_t friendNumber) const
{
QString id = getFriendPublicKey(friendNumber);
@ -1110,6 +1173,9 @@ QString Core::getFriendAddress(uint32_t friendNumber) const
return id;
}
/**
@brief Get the public key part of the ToxID only
*/
QString Core::getFriendPublicKey(uint32_t friendNumber) const
{
uint8_t rawid[TOX_PUBLIC_KEY_SIZE];
@ -1124,6 +1190,9 @@ QString Core::getFriendPublicKey(uint32_t friendNumber) const
return id;
}
/**
@brief Get the username of a friend
*/
QString Core::getFriendUsername(uint32_t friendnumber) const
{
size_t namesize = tox_friend_get_name_size(tox, friendnumber, nullptr);
@ -1197,6 +1266,9 @@ QString Core::getPeerName(const ToxId& id) const
return name;
}
/**
@brief Most of the API shouldn't be used until Core is ready, call start() first
*/
bool Core::isReady()
{
return av && av->getToxAv() && tox && ready;
@ -1211,6 +1283,9 @@ void Core::setNospam(uint32_t nospam)
emit idSet(getSelfId().toString());
}
/**
@brief Returns the unencrypted tox save data
*/
void Core::killTimers(bool onlyStop)
{
assert(QThread::currentThread() == coreThread);
@ -1223,6 +1298,10 @@ void Core::killTimers(bool onlyStop)
}
}
/**
@brief Reinitialized the core.
@warning Must be called from the Core thread, with the GUI thread ready to process events.
*/
void Core::reset()
{
assert(QThread::currentThread() == coreThread);

View File

@ -46,7 +46,7 @@ class Core : public QObject
Q_OBJECT
public:
explicit Core(QThread* coreThread, Profile& profile);
static Core* getInstance(); ///< Returns the global widget's Core instance
static Core* getInstance();
CoreAV* getAv();
~Core();
@ -59,41 +59,42 @@ public:
QString getPeerName(const ToxId& id) const;
QVector<uint32_t> getFriendList() const; ///< Returns the list of friendIds in our friendlist, an empty list on error
int getGroupNumberPeers(int groupId) const; ///< Return the number of peers in the group chat on success, or -1 on failure
QString getGroupPeerName(int groupId, int peerId) const; ///< Get the name of a peer of a group
ToxId getGroupPeerToxId(int groupId, int peerId) const; ///< Get the public key of a peer of a group
QList<QString> getGroupPeerNames(int groupId) const; ///< Get the names of the peers of a group
QString getFriendAddress(uint32_t friendNumber) const; ///< Get the full address if known, or public key of a friend
QString getFriendPublicKey(uint32_t friendNumber) const; ///< Get the public key part of the ToxID only
QString getFriendUsername(uint32_t friendNumber) const; ///< Get the username of a friend
bool isFriendOnline(uint32_t friendId) const; ///< Check if a friend is online. Unknown friends are considered offline.
bool hasFriendWithAddress(const QString &addr) const; ///< Check if we have a friend by address
bool hasFriendWithPublicKey(const QString &pubkey) const; ///< Check if we have a friend by public key
int joinGroupchat(int32_t friendId, uint8_t type, const uint8_t* pubkey,uint16_t length) const; ///< Accept a groupchat invite
void quitGroupChat(int groupId) const; ///< Quit a groupchat
QVector<uint32_t> getFriendList() const;
int getGroupNumberPeers(int groupId) const;
QString getGroupPeerName(int groupId, int peerId) const;
ToxId getGroupPeerToxId(int groupId, int peerId) const;
QList<QString> getGroupPeerNames(int groupId) const;
QString getFriendAddress(uint32_t friendNumber) const;
QString getFriendPublicKey(uint32_t friendNumber) const;
QString getFriendUsername(uint32_t friendNumber) const;
QString getUsername() const; ///< Returns our username, or an empty string on failure
Status getStatus() const; ///< Returns our user status
QString getStatusMessage() const; ///< Returns our status message, or an empty string on failure
ToxId getSelfId() const; ///< Returns our Tox ID
QPair<QByteArray, QByteArray> getKeypair() const; ///< Returns our public and private keys
bool isFriendOnline(uint32_t friendId) const;
bool hasFriendWithAddress(const QString &addr) const;
bool hasFriendWithPublicKey(const QString &pubkey) const;
int joinGroupchat(int32_t friendId, uint8_t type, const uint8_t* pubkey,uint16_t length) const;
void quitGroupChat(int groupId) const;
QString getUsername() const;
Status getStatus() const;
QString getStatusMessage() const;
ToxId getSelfId() const;
QPair<QByteArray, QByteArray> getKeypair() const;
static std::unique_ptr<TOX_PASS_KEY> createPasskey(const QString &password, uint8_t* salt = nullptr);
static QByteArray encryptData(const QByteArray& data, const TOX_PASS_KEY& encryptionKey);
static QByteArray encryptData(const QByteArray& data); ///< Uses the default profile's key
static QByteArray encryptData(const QByteArray& data);
static QByteArray decryptData(const QByteArray& data, const TOX_PASS_KEY &encryptionKey);
static QByteArray decryptData(const QByteArray& data); ///< Uses the default profile's key
static QByteArray decryptData(const QByteArray& data);
bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first
bool isReady();
public slots:
void start(); ///< Initializes the core, must be called before anything else
void reset(); ///< Reinitialized the core. Must be called from the Core thread, with the GUI thread ready to process events.
void process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer
void bootstrapDht(); ///< Connects us to the Tox network
void start();
void reset();
void process();
void bootstrapDht();
QByteArray getToxSaveData(); ///< Returns the unencrypted tox save data
QByteArray getToxSaveData();
void acceptFriendRequest(const QString& userId);
void requestFriendship(const QString& friendAddress, const QString& message);
@ -225,7 +226,7 @@ private:
void deadifyTox();
private slots:
void killTimers(bool onlyStop); ///< Must only be called from the Core thread
void killTimers(bool onlyStop);
private:
Tox* tox;

View File

@ -31,7 +31,52 @@
#include <QCoreApplication>
#include <QtConcurrent/QtConcurrentRun>
/**
@fn void CoreAV::avInvite(uint32_t friendId, bool video)
@brief Sent when a friend calls us.
@param friendId Id of friend in call list.
@param video False if chat is audio only, true audio and video.
@fn void CoreAV::avStart(uint32_t friendId, bool video)
@brief Sent when a call we initiated has started.
@param friendId Id of friend in call list.
@param video False if chat is audio only, true audio and video.
@fn void CoreAV::avEnd(uint32_t friendId)
@brief Sent when a call was ended by the peer.
@param friendId Id of friend in call list.
*/
/**
@var CoreAV::AUDIO_DEFAULT_BITRATE
@brief In kb/s. More than enough for Opus.
@var CoreAV::VIDEO_DEFAULT_BITRATE
@brief Picked at random by fair dice roll.
*/
/**
@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.
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.
*/
/**
@brief Maps friend IDs to ToxFriendCall.
*/
IndexedList<ToxFriendCall> CoreAV::calls;
/**
@brief Maps group IDs to ToxGroupCalls.
*/
IndexedList<ToxGroupCall> CoreAV::groupCalls;
using namespace std;
@ -76,6 +121,9 @@ const ToxAV *CoreAV::getToxAv() const
return toxav;
}
/**
@brief Starts the CoreAV main loop that calls toxav's main loop
*/
void CoreAV::start()
{
// Timers can only be touched from their own thread
@ -84,6 +132,9 @@ void CoreAV::start()
iterateTimer->start();
}
/**
@brief Stops the main loop
*/
void CoreAV::stop()
{
// Timers can only be touched from their own thread
@ -92,6 +143,9 @@ void CoreAV::stop()
iterateTimer->stop();
}
/**
@brief Calls itself blocking queued on the coreav thread
*/
void CoreAV::killTimerFromThread()
{
// Timers can only be touched from their own thread
@ -106,6 +160,11 @@ void CoreAV::process()
iterateTimer->start(toxav_iteration_interval(toxav));
}
/**
@brief Check, that and calls are active now
@return True is any calls are currently active, False otherwise
@note A call about to start is not yet active
*/
bool CoreAV::anyActiveCalls()
{
return !calls.isEmpty();
@ -234,6 +293,15 @@ void CoreAV::timeoutCall(uint32_t friendNum)
emit avEnd(friendNum);
}
/**
@brief Send audio frame to a friend
@param callId Id of friend in call list.
@param pcm An array of audio samples (Pulse-code modulation).
@param samples Number of samples in this frame.
@param chans Number of audio channels.
@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)
{
if (!calls.contains(callId))
@ -378,6 +446,11 @@ void CoreAV::groupCallCallback(void* tox, int group, int peer,
sample_rate);
}
/**
@brief Get a call's video source.
@param friendNum Id of friend in call list.
@return Video surface to show
*/
VideoSource *CoreAV::getVideoSourceFromCall(int friendNum)
{
if (!calls.contains(friendNum))
@ -389,6 +462,11 @@ VideoSource *CoreAV::getVideoSourceFromCall(int friendNum)
return calls[friendNum].videoSource;
}
/**
@brief Starts a call in an existing AV groupchat.
@note Call from the GUI thread.
@param groupId Id of group to join
*/
void CoreAV::joinGroupCall(int groupId)
{
qDebug() << QString("Joining group call %1").arg(groupId);
@ -397,6 +475,11 @@ void CoreAV::joinGroupCall(int groupId)
call->inactive = false;
}
/**
@brief Will not leave the group, just stop the call.
@note Call from the GUI thread.
@param groupId Id of group to leave
*/
void CoreAV::leaveGroupCall(int groupId)
{
qDebug() << QString("Leaving group call %1").arg(groupId);
@ -450,11 +533,19 @@ bool CoreAV::isGroupCallVolEnabled(int groupId) const
return !groupCalls[groupId].muteVol;
}
/**
@brief Check, that group has audio or video stream
@param groupId Id of group to check
@return True for AV groups, false for text-only groups
*/
bool CoreAV::isGroupAvEnabled(int groupId) const
{
return tox_group_get_type(Core::getInstance()->tox, groupId) == TOX_GROUPCHAT_TYPE_AV;
}
/**
@brief Forces to regenerate each call's audio sources.
*/
void CoreAV::invalidateCallSources()
{
for (ToxGroupCall& call : groupCalls)
@ -468,6 +559,10 @@ void CoreAV::invalidateCallSources()
}
}
/**
@brief Signal to all peers that we're not sending video anymore.
@note The next frame sent cancels this.
*/
void CoreAV::sendNoVideo()
{
// We don't change the audio bitrate, but we signal that we're not sending video anymore

View File

@ -46,26 +46,25 @@ public:
const ToxAV* getToxAv() const;
bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
bool anyActiveCalls();
bool isCallVideoEnabled(uint32_t friendNum);
/// Returns false only on error, but not if there's nothing to send
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);
VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source
void invalidateCallSources(); ///< Forces to regenerate each call's audio sources
void sendNoVideo(); ///< Signal to all peers that we're not sending video anymore. The next frame sent cancels this.
VideoSource* getVideoSourceFromCall(int callNumber);
void invalidateCallSources();
void sendNoVideo();
void joinGroupCall(int groupNum); ///< Starts a call in an existing AV groupchat. Call from the GUI thread.
void leaveGroupCall(int groupNum); ///< Will not leave the group, just stop the call. Call from the GUI thread.
void joinGroupCall(int groupNum);
void leaveGroupCall(int groupNum);
void disableGroupCallMic(int groupNum);
void disableGroupCallVol(int groupNum);
void enableGroupCallMic(int groupNum);
void enableGroupCallVol(int groupNum);
bool isGroupCallMicEnabled(int groupNum) const;
bool isGroupCallVolEnabled(int groupNum) const;
bool isGroupAvEnabled(int groupNum) const; ///< True for AV groups, false for text-only groups
bool isGroupAvEnabled(int groupNum) const;
void micMuteToggle(uint32_t friendNum);
void volMuteToggle(uint32_t friendNum);
@ -80,21 +79,19 @@ public slots:
bool answerCall(uint32_t friendNum);
bool cancelCall(uint32_t friendNum);
void timeoutCall(uint32_t friendNum);
/// Starts the CoreAV main loop that calls toxav's main loop
void start();
/// Stops the main loop
void stop();
signals:
void avInvite(uint32_t friendId, bool video); ///< Sent when a friend calls us
void avStart(uint32_t friendId, bool video); ///< Sent when a call we initiated has started
void avEnd(uint32_t friendId); ///< Sent when a call was ended by the peer
void avInvite(uint32_t friendId, bool video);
void avStart(uint32_t friendId, bool video);
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);
void killTimerFromThread(); ///< Calls itself blocking queued on the coreav thread
void killTimerFromThread();
private:
void process();
@ -105,27 +102,15 @@ private:
int32_t ystride, int32_t ustride, int32_t vstride, void* self);
private:
static constexpr uint32_t AUDIO_DEFAULT_BITRATE = 64; ///< In kb/s. More than enough for Opus.
static constexpr uint32_t VIDEO_DEFAULT_BITRATE = 6144; ///< Picked at random by fair dice roll.
static constexpr uint32_t AUDIO_DEFAULT_BITRATE = 64;
static constexpr uint32_t VIDEO_DEFAULT_BITRATE = 6144;
private:
ToxAV* toxav;
std::unique_ptr<QThread> coreavThread;
std::unique_ptr<QTimer> iterateTimer;
static IndexedList<ToxFriendCall> calls;
static IndexedList<ToxGroupCall> groupCalls; // Maps group IDs to ToxGroupCalls
/**
* 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.
*
* 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.
*/
static IndexedList<ToxGroupCall> groupCalls;
std::atomic_flag threadSwitchLock;
friend class Audio;

View File

@ -51,6 +51,12 @@ std::unique_ptr<TOX_PASS_KEY> Core::createPasskey(const QString& password, uint8
return encryptionKey;
}
/**
@brief Encrypts data.
@note Uses the default profile's key.
@param data Data to encrypt.
@return Encrypted data.
*/
QByteArray Core::encryptData(const QByteArray &data)
{
return encryptData(data, Nexus::getProfile()->getPasskey());
@ -68,6 +74,12 @@ QByteArray Core::encryptData(const QByteArray& data, const TOX_PASS_KEY& encrypt
return QByteArray(reinterpret_cast<char*>(encrypted), data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH);
}
/**
@brief Decrypts data.
@note Uses the default profile's key.
@param data Data to decrypt.
@return Decrypted data.
*/
QByteArray Core::decryptData(const QByteArray &data)
{
return decryptData(data, Nexus::getProfile()->getPasskey());

View File

@ -30,14 +30,27 @@
#include <QDir>
#include <memory>
/**
@class CoreFile
@brief Implements Core's file transfer callbacks.
Avoids polluting core.h with private internal callbacks.
*/
QMutex CoreFile::fileSendMutex;
QHash<uint64_t, ToxFile> CoreFile::fileMap;
using namespace std;
/**
@brief Get corefile iteration interval.
tox_iterate calls to get good file transfer performances
@return The maximum amount of time in ms that Core should wait between two tox_iterate() calls.
*/
unsigned CoreFile::corefileIterationInterval()
{
/// Sleep at most 1000ms if we have no FT, 10 for user FTs, 50 for the rest (avatars, ...)
constexpr unsigned fastFileInterval=10, slowFileInterval=50, idleInterval=1000;
constexpr unsigned fastFileInterval = 10, slowFileInterval = 50, idleInterval = 1000;
unsigned interval = idleInterval;
for (ToxFile& file : fileMap)

View File

@ -35,8 +35,6 @@
struct Tox;
class Core;
/// Implements Core's file transfer callbacks
/// Avoids polluting core.h with private internal callbacks
class CoreFile
{
friend class Core;
@ -57,8 +55,6 @@ private:
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);
/// Returns the maximum amount of time in ms that Core should wait between two
/// tox_iterate calls to get good file transfer performances
static unsigned corefileIterationInterval();
private:

View File

@ -6,6 +6,18 @@
#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE
/**
@file corestructs.h
@brief Some headers use Core structs but don't need to include all of core.h
They should include this file directly instead to reduce compilation times
*/
/**
@var uint8_t ToxFile::fileKind
@brief Data file (default) or avatar
*/
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},

View File

@ -1,11 +1,9 @@
#ifndef CORESTRUCTS_H
#define CORESTRUCTS_H
// Some headers use Core structs but don't need to include all of core.h
// They should include this file directly instead to reduce compilation times
#include <QString>
#include <memory>
class QFile;
class QTimer;
@ -35,7 +33,7 @@ struct ToxFile
RECEIVING
};
ToxFile()=default;
ToxFile() = default;
ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString filePath, FileDirection Direction);
~ToxFile(){}
@ -45,7 +43,7 @@ struct ToxFile
void setFilePath(QString path);
bool open(bool write);
uint8_t fileKind; ///< Data file (default) or avatar
uint8_t fileKind;
uint32_t fileNum;
uint32_t friendId;
QByteArray fileName;

View File

@ -4,8 +4,6 @@
#include <vector>
#include <algorithm>
/// More or less a hashmap, indexed by the noexcept move-only value type's operator int
/// Really nice to store our ToxCall objects.
template <typename T>
class IndexedList
{
@ -13,30 +11,72 @@ public:
explicit IndexedList() = default;
// Qt
inline bool isEmpty() { return v.empty(); }
bool contains(int i) { return std::find_if(begin(), end(), [i](T& t){return (int)t == i;}) != end(); }
void remove(int i) { v.erase(std::remove_if(begin(), end(), [i](T& t){return (int)t == i;}), end()); }
T& operator[](int i)
bool isEmpty()
{
return v.empty();
}
bool contains(int i)
{
return std::find_if(begin(), end(), [i](T& t){return (int)t == i;}) != end();
}
void remove(int i)
{
v.erase(std::remove_if(begin(), end(), [i](T& t){return (int)t == i;}), end());
}
T &operator[](int i)
{
iterator it = std::find_if(begin(), end(), [i](T& t){return (int)t == i;});
if (it == end())
it = insert({});
return *it;
}
// STL
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
inline iterator begin() { return v.begin(); }
inline const_iterator begin() const { return v.begin(); }
inline const_iterator cbegin() const { return v.cbegin(); }
inline iterator end() { return v.end(); }
inline const_iterator end() const { return v.end(); }
inline const_iterator cend() const { return v.cend(); }
inline iterator erase(iterator pos) { return v.erase(pos); }
inline iterator erase(iterator first, iterator last) { return v.erase(first, last); }
inline iterator insert(T&& value) { v.push_back(std::move(value)); return --v.end(); }
inline iterator begin()
{
return v.begin();
}
inline const_iterator begin() const
{
return v.begin();
}
inline const_iterator cbegin() const
{
return v.cbegin();
}
inline iterator end()
{
return v.end();
}
inline const_iterator end() const
{
return v.end();
}
inline const_iterator cend() const
{
return v.cend();
}
inline iterator erase(iterator pos)
{
return v.erase(pos);
}
inline iterator erase(iterator first, iterator last)
{
return v.erase(first, last);
}
inline iterator insert(T&& value)
{
v.push_back(std::move(value));
return --v.end();
}
private:
std::vector<T> v;

View File

@ -7,6 +7,23 @@
#include <QTimer>
#include <QtConcurrent/QtConcurrent>
/**
@var uint32_t ToxCall::callId
@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, ...)
@var bool ToxCall::videoEnabled
@brief True if our user asked for a video call, sending and recieving.
@var bool ToxCall::nullVideoBitrate
@brief True if our video bitrate is zero, i.e. if the device is closed.
@var TOXAV_FRIEND_CALL_STATE ToxCall::state
@brief State of the peer (not ours!)
*/
using namespace std;
ToxCall::ToxCall(uint32_t CallId)

View File

@ -32,9 +32,9 @@ protected:
QMetaObject::Connection audioInConn;
public:
uint32_t callId; ///< Could be a friendNum or groupNum, must uniquely identify the call. Do not modify!
uint32_t callId;
quint32 alSource;
bool inactive; ///< True while we're not participating. (stopped group call, ringing but hasn't started yet, ...)
bool inactive;
bool muteMic;
bool muteVol;
};
@ -48,10 +48,10 @@ struct ToxFriendCall : public ToxCall
ToxFriendCall& operator=(ToxFriendCall&& other) noexcept;
bool videoEnabled; ///< True if our user asked for a video call, sending and recving
bool nullVideoBitrate; ///< True if our video bitrate is zero, i.e. if the device is closed
bool videoEnabled;
bool nullVideoBitrate;
CoreVideoSource* videoSource;
TOXAV_FRIEND_CALL_STATE state; ///< State of the peer (not ours!)
TOXAV_FRIEND_CALL_STATE state;
void startTimeout();
void stopTimeout();

View File

@ -30,14 +30,46 @@
#define TOX_ID_CHECKSUM_LENGTH 4
#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE
/**
@class ToxId
@brief This class represents a Tox ID.
An ID is composed of 32 bytes long public key, 4 bytes long NoSpam
and 2 bytes long checksum.
e.g.
@code
| C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30 | C8BA3AB9 | BEB9
| / |
| / NoSpam | Checksum
| Public Key (PK), 32 bytes, 64 characters / 4 bytes | 2 bytes
| | 8 characters| 4 characters
@endcode
*/
/**
@brief The default constructor. Creates an empty Tox ID.
*/
ToxId::ToxId()
: publicKey(), noSpam(), checkSum()
{}
/**
@brief The copy constructor.
@param other ToxId to copy
*/
ToxId::ToxId(const ToxId &other)
: publicKey(other.publicKey), noSpam(other.noSpam), checkSum(other.checkSum)
{}
/**
@brief Create a Tox ID from QString.
If the given id is not a valid Tox ID, then:
publicKey == id and noSpam == "" == checkSum.
@param id Tox ID string to convert to ToxId object
*/
ToxId::ToxId(const QString &id)
{
if (isToxId(id))
@ -52,26 +84,48 @@ ToxId::ToxId(const QString &id)
}
}
/**
@brief Compares, that public key equals.
@param other Tox ID to compare.
@return True if both Tox ID have same public keys, false otherwise.
*/
bool ToxId::operator==(const ToxId& other) const
{
return publicKey == other.publicKey;
}
/**
@brief Compares, that only public key not equals.
@param other Tox ID to compare.
@return True if both Tox ID have different public keys, false otherwise.
*/
bool ToxId::operator!=(const ToxId &other) const
{
return publicKey != other.publicKey;
}
/**
@brief Check, that the current user ID is the active user ID
@return True if this Tox ID is equals to
the Tox ID of the currently active profile.
*/
bool ToxId::isSelf() const
{
return *this == Core::getInstance()->getSelfId();
}
/**
@brief Returns Tox ID converted to QString.
@return The Tox ID as QString.
*/
QString ToxId::toString() const
{
return publicKey + noSpam + checkSum;
}
/**
@brief Clears all elements of the Tox ID.
*/
void ToxId::clear()
{
publicKey.clear();
@ -79,6 +133,11 @@ void ToxId::clear()
checkSum.clear();
}
/**
@brief Check, that id is a valid Tox ID.
@param id Tox ID to check.
@return True if id is a valid Tox ID, false otherwise.
*/
bool ToxId::isToxId(const QString &id)
{
const QRegularExpression hexRegExp("^[A-Fa-f0-9]+$");

View File

@ -23,36 +23,20 @@
#include <QString>
/*
* This class represents a Tox ID.
* An ID is composed of 32 bytes long public key, 4 bytes long NoSpam
* and 2 bytes long checksum.
*
* e.g.
*
* | C7719C6808C14B77348004956D1D98046CE09A34370E7608150EAD74C3815D30 | C8BA3AB9 | BEB9
* | / |
* | / NoSpam | Checksum
* | Public Key (PK), 32 bytes, 64 characters / 4 bytes | 2 bytes
* | | 8 characters| 4 characters
*/
class ToxId
{
public:
ToxId(); ///< The default constructor. Creates an empty Tox ID.
ToxId(const ToxId& other); ///< The copy constructor.
explicit ToxId(const QString& id); ///< Create a Tox ID from QString.
/// If the given id is not a valid Tox ID, then:
/// publicKey == id and noSpam == "" == checkSum.
ToxId();
ToxId(const ToxId& other);
explicit ToxId(const QString& id);
bool operator==(const ToxId& other) const; ///< Compares only publicKey.
bool operator!=(const ToxId& other) const; ///< Compares only publicKey.
bool isSelf() const; ///< Returns true if this Tox ID is equals to
/// the Tox ID of the currently active profile.
QString toString() const; ///< Returns the Tox ID as QString.
void clear(); ///< Clears all elements of the Tox ID.
bool operator==(const ToxId& other) const;
bool operator!=(const ToxId& other) const;
bool isSelf() const;
QString toString() const;
void clear();
static bool isToxId(const QString& id); ///< Returns true if id is a valid Tox ID.
static bool isToxId(const QString& id);
public:
QString publicKey;

View File

@ -51,6 +51,9 @@ Friend::~Friend()
delete widget;
}
/**
@brief Loads the friend's chat history if enabled
*/
void Friend::loadHistory()
{
if (Nexus::getProfile()->isHistoryEnabled())

View File

@ -37,7 +37,6 @@ public:
~Friend();
Friend& operator=(const Friend& other)=delete;
/// Loads the friend's chat history if enabled
void loadHistory();
void setName(QString name);

View File

@ -25,6 +25,19 @@
#include <random>
#include <unistd.h>
/**
@var time_t IPC::lastEvent
@brief When last event was posted.
@var time_t IPC::lastProcessed
@brief When processEvents() ran last time
*/
/**
@class IPC
@brief Inter-process communication
*/
IPC::IPC()
: globalMemory{"qtox-" IPC_PROTOCOL_VERSION}
{
@ -84,13 +97,23 @@ IPC::~IPC()
}
}
/**
@brief Returns the singleton instance.
*/
IPC& IPC::getInstance()
{
static IPC instance;
return instance;
}
time_t IPC::postEvent(const QString &name, const QByteArray& data/*=QByteArray()*/, uint32_t dest/*=0*/)
/**
@brief Post IPC event.
@param name Name to set in IPC event.
@param data Data to set in IPC event (default QByteArray()).
@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)
{
QByteArray binName = name.toUtf8();
if (binName.length() > (int32_t)sizeof(IPCEvent::name))
@ -101,7 +124,7 @@ time_t IPC::postEvent(const QString &name, const QByteArray& data/*=QByteArray()
if (globalMemory.lock())
{
IPCEvent* evt = 0;
IPCEvent* evt = nullptr;
IPCMemory* mem = global();
time_t result = 0;
@ -195,6 +218,10 @@ bool IPC::waitUntilAccepted(time_t postTime, int32_t timeout/*=-1*/)
return result;
}
/**
@brief Only called when global memory IS LOCKED.
@return nullptr if no evnts present, IPC event otherwise
*/
IPC::IPCEvent *IPC::fetchEvent()
{
IPCMemory* mem = global();
@ -209,32 +236,28 @@ IPC::IPCEvent *IPC::fetchEvent()
(!evt->processed && difftime(time(0), evt->posted) > EVENT_GC_TIMEOUT))
memset(evt, 0, sizeof(IPCEvent));
if (evt->posted && !evt->processed && evt->sender != getpid())
{
if (evt->dest == Settings::getInstance().getCurrentProfileId() || (evt->dest == 0 && isCurrentOwner()))
return evt;
}
if (evt->posted && !evt->processed && evt->sender != getpid()
&& (evt->dest == Settings::getInstance().getCurrentProfileId()
|| (evt->dest == 0 && isCurrentOwner())))
return evt;
}
return 0;
return nullptr;
}
bool IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg)
{
bool result = false;
if (QThread::currentThread() != qApp->thread())
{
if (QThread::currentThread() == qApp->thread())
result = handler(arg);
else
QMetaObject::invokeMethod(this, "runEventHandler",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, result),
Q_ARG(IPCEventHandler, handler),
Q_ARG(const QByteArray&, arg));
return result;
}
else
{
result = handler(arg);
return result;
}
return result;
}
void IPC::processEvents()

View File

@ -64,14 +64,11 @@ public:
struct IPCMemory
{
uint64_t globalId;
// When last event was posted
time_t lastEvent;
// When processEvents() ran last time
time_t lastProcessed;
IPCEvent events[IPC::EVENT_QUEUE_SIZE];
};
// dest: Settings::getCurrentProfileId() or 0 (main instance).
time_t postEvent(const QString& name, const QByteArray &data=QByteArray(), uint32_t dest=0);
bool isCurrentOwner();
void registerEventHandler(const QString& name, IPCEventHandler handler);
@ -84,7 +81,6 @@ protected slots:
protected:
IPCMemory* global();
bool runEventHandler(IPCEventHandler handler, const QByteArray& arg);
// Only called when global memory IS LOCKED, returns 0 if no evnts present
IPCEvent* fetchEvent();
QTimer timer;

View File

@ -17,7 +17,6 @@
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "src/net/autoupdate.h"
#include "src/persistence/serialize.h"
#include "src/persistence/settings.h"
@ -40,6 +39,13 @@
#include <shellapi.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.
*/
#ifdef Q_OS_WIN
#ifdef Q_OS_WIN64
const QString AutoUpdater::platform = "win64";
@ -72,6 +78,50 @@ const QString AutoUpdater::updaterBin;
const QString AutoUpdater::updateServer;
unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES];
#endif
/**
@var unsigned char AutoUpdater::UpdateFileMeta::sig[crypto_sign_BYTES]
@brief Signature of the file (ed25519)
@var QString AutoUpdater::UpdateFileMeta::id
@brief Unique id of the file
@var QString AutoUpdater::UpdateFileMeta::installpath
@brief Local path including the file name. May be relative to qtox-updater or absolute
@var uint64_t AutoUpdater::UpdateFileMeta::size
@brief Size in bytes of the file
*/
/**
@var static const QString AutoUpdater::updateServer
@brief Hostname of the qTox update server
@var static const QString AutoUpdater::platform
@brief Name of platform we're trying to get updates for
@var static const QString AutoUpdater::checkURI
@brief URI of the file containing the latest version string
@var static const QString AutoUpdater::flistURI
@brief URI of the file containing info on each file (hash, signature, size, name, ..)
@var static const QString AutoUpdater::filesURI
@brief URI of the actual files of the latest version
@var static const QString AutoUpdater::updaterBin
@brief Path to the qtox-updater binary
@var static std::atomic_bool AutoUpdater::abortFlag
@brief If true, try to abort everything.
@var static std::atomic_bool AutoUpdater::isDownloadingUpdate
@brief We'll pretend there's no new update available if we're already updating
@var static QMutex AutoUpdater::progressVersionMutex
@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/";
@ -81,6 +131,19 @@ std::atomic<float> AutoUpdater::progressValue{0};
QString AutoUpdater::progressVersion;
QMutex AutoUpdater::progressVersionMutex;
/**
@class AutoUpdater
@brief Handles checking and applying updates for qTox.
@note Do *NOT* use auto update unless AUTOUPDATE_ENABLED is defined to 1.
*/
/**
@brief Checks if an update is available for download.
@return True if an update is available for download, false otherwise.
Connects to the qTox update server, and check if an update is available for download
Will call getUpdateVersion, and as such may block and processEvents.
*/
bool AutoUpdater::isUpdateAvailable()
{
if (isDownloadingUpdate)
@ -95,6 +158,11 @@ bool AutoUpdater::isUpdateAvailable()
return !diff.isEmpty();
}
/**
@brief Fetch the version info of the last update available from the qTox update server
@note Will try to follow qTox's proxy settings, may block and processEvents
@return Avaliable version info.
*/
AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
{
VersionInfo versionInfo;
@ -159,6 +227,11 @@ AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion()
return versionInfo;
}
/**
@brief Parses and validates a flist file.
@param flistData Install file data.
@return An empty list on error.
*/
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseFlist(QByteArray flistData)
{
QList<UpdateFileMeta> flist;
@ -218,6 +291,11 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseFlist(QByteArray flistData)
return flist;
}
/**
@brief Gets the update server's flist.
@note Will try to follow qTox's proxy settings, may block and processEvents
@return An empty array on error
*/
QByteArray AutoUpdater::getUpdateFlist()
{
QByteArray flist;
@ -247,6 +325,11 @@ QByteArray AutoUpdater::getUpdateFlist()
return flist;
}
/**
@brief Generates a list of files we need to update.
@param updateFlist List of files available to update.
@return List of files we need to update.
*/
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::genUpdateDiff(QList<UpdateFileMeta> updateFlist)
{
QList<UpdateFileMeta> diff;
@ -258,6 +341,11 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::genUpdateDiff(QList<UpdateFileMe
return diff;
}
/**
@brief Checks if we have an up to date version of this file locally installed.
@param fileMeta File to check.
@return True if file doesn't need updates, false if need.
*/
bool AutoUpdater::isUpToDate(AutoUpdater::UpdateFileMeta fileMeta)
{
QString appDir = qApp->applicationDirPath();
@ -273,6 +361,14 @@ bool AutoUpdater::isUpToDate(AutoUpdater::UpdateFileMeta fileMeta)
return true;
}
/**
@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 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
@return A file with a null QByteArray on error.
*/
AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta,
std::function<void(int,int)> progressCallback)
{
@ -305,7 +401,11 @@ AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta,
return file;
}
/**
@brief Will try to download an update, if successful qTox will apply it after a restart
@note Will try to follow qTox's proxy settings, may block and processEvents
@result True if successful and qTox will apply it after a restart
*/
bool AutoUpdater::downloadUpdate()
{
// Updates only for supported platforms
@ -433,6 +533,13 @@ fail:
return false;
}
/**
@brief Checks if an update is downloaded and ready to be installed.
@note If result is true, call installLocalUpdate,
@return True if an update is downloaded, false if partially downloaded.
If an update was partially downloaded, the function will resume asynchronously and return false.
*/
bool AutoUpdater::isLocalUpdateReady()
{
// Updates only for supported platforms
@ -472,6 +579,13 @@ bool AutoUpdater::isLocalUpdateReady()
return true;
}
/**
@brief Launches the qTox updater to try to install the local update and exits immediately.
@note Will not check that the update actually exists, use isLocalUpdateReady first for that.
The qTox updater will restart us after the update is done.
If we fail to start the qTox updater, we will delete the update and exit.
*/
void AutoUpdater::installLocalUpdate()
{
qDebug() << "About to start the qTox updater to install a local update";
@ -511,6 +625,14 @@ void AutoUpdater::installLocalUpdate()
exit(0);
}
/**
@brief Checks update an show dialog asking to download it.
@note Runs asynchronously in its own thread, and will return immediatly
Will call isUpdateAvailable, and as such may processEvents.
Connects to the qTox update server, if an update is found
shows a dialog to the user asking to download it.
*/
void AutoUpdater::checkUpdatesAsyncInteractive()
{
if (isDownloadingUpdate)
@ -519,6 +641,11 @@ void AutoUpdater::checkUpdatesAsyncInteractive()
QtConcurrent::run(&AutoUpdater::checkUpdatesAsyncInteractiveWorker);
}
/**
@brief Does the actual work for checkUpdatesAsyncInteractive
Blocking, but otherwise has the same properties than checkUpdatesAsyncInteractive
*/
void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
{
if (!isUpdateAvailable())
@ -557,18 +684,32 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker()
}
}
/**
@brief Thread safe setter
@param version Version to set.
*/
void AutoUpdater::setProgressVersion(QString version)
{
QMutexLocker lock(&progressVersionMutex);
progressVersion = version;
}
/**
@brief Abort update process.
@note Aborting will make some functions try to return early.
Call before qTox exits to avoid the updater running in the background.
*/
void AutoUpdater::abortUpdates()
{
abortFlag = true;
isDownloadingUpdate = false;
}
/**
@brief Functions giving info on the progress of update downloads.
@return Version as string.
*/
QString AutoUpdater::getProgressVersion()
{
QMutexLocker lock(&progressVersionMutex);

View File

@ -28,8 +28,6 @@
#include <atomic>
#include <functional>
/// 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
#define AUTOUPDATE_ENABLED 1
#elif defined(Q_OS_OSX)
@ -38,17 +36,15 @@
#define AUTOUPDATE_ENABLED 0
#endif
/// Handles checking and applying updates for qTox
/// Do *NOT* use auto update unless AUTOUPDATE_ENABLED is defined to 1
class AutoUpdater
{
public:
struct UpdateFileMeta
{
unsigned char sig[crypto_sign_BYTES]; ///< Signature of the file (ed25519)
QString id; ///< Unique id of the file
QString installpath; ///< Local path including the file name. May be relative to qtox-updater or absolute
uint64_t size; ///< Size in bytes of the file
unsigned char sig[crypto_sign_BYTES];
QString id;
QString installpath;
uint64_t size;
bool operator==(const UpdateFileMeta& other)
{
@ -71,72 +67,41 @@ public:
};
public:
/// Connects to the qTox update server, if an updat is found shows a dialog to the user asking to download it
/// Runs asynchronously in its own thread, and will return immediatly
/// Will call isUpdateAvailable, and as such may processEvents
static void checkUpdatesAsyncInteractive();
/// Connects to the qTox update server, returns true if an update is available for download
/// Will call getUpdateVersion, and as such may block and processEvents
static bool isUpdateAvailable();
/// Fetch the version info of the last update available from the qTox update server
/// Will try to follow qTox's proxy settings, may block and processEvents
static VersionInfo getUpdateVersion();
/// Will try to download an update, if successful returns true and qTox will apply it after a restart
/// Will try to follow qTox's proxy settings, may block and processEvents
static bool downloadUpdate();
/// Returns true if an update is downloaded and ready to be installed,
/// if so, call installLocalUpdate.
/// If an update was partially downloaded, the function will resume asynchronously and return false
static bool isLocalUpdateReady();
/// Launches the qTox updater to try to install the local update and exits immediately
/// Will not check that the update actually exists, use isLocalUpdateReady first for that
/// The qTox updater will restart us after the update is done
/// Note: If we fail to start the qTox updater, we will delete the update and exit
[[ noreturn ]] static void installLocalUpdate();
/// Aborting will make some functions try to return early
/// Call before qTox exits to avoid the updater running in the background
static void abortUpdates();
/// Functions giving info on the progress of update downloads
static QString getProgressVersion();
static int getProgressValue();
protected:
/// Parses and validates a flist file. Returns an empty list on error
static QList<UpdateFileMeta> parseFlist(QByteArray flistData);
/// Gets the update server's flist. Returns an empty array on error
/// Will try to follow qTox's proxy settings, may block and processEvents
static QByteArray getUpdateFlist();
/// Generates a list of files we need to update
static QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist);
/// Checks if we have an up to date version of this file locally installed
static bool isUpToDate(UpdateFileMeta file);
/// Tries to fetch the file from the update server. Returns a file with a null QByteArray on error.
/// Note that a file with an empty but non-null QByteArray is not an error, merely a file of size 0.
/// Will try to follow qTox's proxy settings, may block and processEvents
static UpdateFile getUpdateFile(UpdateFileMeta fileMeta, std::function<void(int,int)> progressCallback);
/// Does the actual work for checkUpdatesAsyncInteractive
/// Blocking, but otherwise has the same properties than checkUpdatesAsyncInteractive
static void checkUpdatesAsyncInteractiveWorker();
/// Thread safe setter
static void setProgressVersion(QString version);
private:
AutoUpdater() = delete;
private:
// Constants
static const QString updateServer; ///< Hostname of the qTox update server
static const QString platform; ///< Name of platform we're trying to get updates for
static const QString checkURI; ///< URI of the file containing the latest version string
static const QString flistURI; ///< URI of the file containing info on each file (hash, signature, size, name, ..)
static const QString filesURI; ///< URI of the actual files of the latest version
static const QString updaterBin; ///< Path to the qtox-updater binary
static const QString updateServer;
static const QString platform;
static const QString checkURI;
static const QString flistURI;
static const QString filesURI;
static const QString updaterBin;
static unsigned char key[];
static std::atomic_bool abortFlag; ///< If true, try to abort everything.
static std::atomic_bool isDownloadingUpdate; ///< We'll pretend there's no new update available if we're already updating
static std::atomic_bool abortFlag;
static std::atomic_bool isDownloadingUpdate;
static std::atomic<float> progressValue;
static QString progressVersion;
static QMutex progressVersionMutex; ///< No, we can't just make the QString atomic
static QMutex progressVersionMutex;
};
#endif // AUTOUPDATE_H

View File

@ -23,6 +23,14 @@
#include <QObject>
#include <QDebug>
/**
@class AvatarBroadcaster
Takes care of broadcasting avatar changes to our friends in a smart way
Cache a copy of our current avatar and friends who have received it
so we don't spam avatar transfers to a friend who already has it.
*/
QByteArray AvatarBroadcaster::avatarData;
QMap<uint32_t, bool> AvatarBroadcaster::friendsSentTo;
@ -32,10 +40,15 @@ static auto autoBroadcast = [](uint32_t friendId, Status)
AvatarBroadcaster::sendAvatarTo(friendId);
};
/**
@brief Set our current avatar.
@param data Byte array on avater.
*/
void AvatarBroadcaster::setAvatar(QByteArray data)
{
if (avatarData == data)
return;
avatarData = data;
friendsSentTo.clear();
@ -44,6 +57,10 @@ void AvatarBroadcaster::setAvatar(QByteArray data)
sendAvatarTo(friendId);
}
/**
@brief Send our current avatar to this friend, if not already sent
@param friendId Id of friend to send avatar.
*/
void AvatarBroadcaster::sendAvatarTo(uint32_t friendId)
{
if (friendsSentTo.contains(friendId) && friendsSentTo[friendId])
@ -54,6 +71,10 @@ void AvatarBroadcaster::sendAvatarTo(uint32_t friendId)
friendsSentTo[friendId] = true;
}
/**
@brief Setup auto broadcast sending avatar.
@param state If true, we automatically broadcast our avatar to friends when they come online.
*/
void AvatarBroadcaster::enableAutoBroadcast(bool state)
{
QObject::disconnect(autoBroadcastConn);

View File

@ -24,20 +24,14 @@
#include <QByteArray>
#include <QMap>
/// Takes care of broadcasting avatar changes to our friends in a smart way
/// Cache a copy of our current avatar and friends who have received it
/// so we don't spam avatar transfers to a friend who already has it.
class AvatarBroadcaster
{
private:
AvatarBroadcaster()=delete;
public:
/// Set our current avatar
static void setAvatar(QByteArray data);
/// Send our current avatar to this friend, if not already sent
static void sendAvatarTo(uint32_t friendId);
/// If true, we automatically broadcast our avatar to friends when they come online
static void enableAutoBroadcast(bool state = true);
private:

View File

@ -29,6 +29,22 @@
#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE
/**
@class ToxDNS
@brief Handles tox1 and tox3 DNS queries.
*/
/**
@struct tox3_server
@brief Represents a tox3 server.
@var const char* tox3_server::name
@brief Hostname of the server, e.g. toxme.se.
@var uint8_t* tox3_server::pubkey
@brief Public key of the tox3 server, usually 256bit long.
*/
const ToxDNS::tox3_server ToxDNS::pinnedServers[]
{
{"toxme.se", (uint8_t[32]){0x5D, 0x72, 0xC5, 0x17, 0xDF, 0x6A, 0xEC, 0x54, 0xF1, 0xE9, 0x77, 0xA6, 0xB6, 0xF2, 0x59, 0x14,
@ -50,10 +66,14 @@ void ToxDNS::showWarning(const QString &message)
warning.exec();
}
/**
@brief Try to fetch the first entry of the given TXT record.
@param record Record to search.
@param silent May display message boxes on error if silent is false.
@return An empty object on failure. May block for up to ~3s.
*/
QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
{
QByteArray result;
QDnsLookup dns;
dns.setType(QDnsLookup::TXT);
dns.setName(record);
@ -71,7 +91,7 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
if (!silent)
showWarning(tr("The connection timed out","The DNS gives the Tox ID associated to toxme.se addresses"));
return result;
return QByteArray();
}
if (dns.error() == QDnsLookup::NotFoundError)
@ -79,14 +99,14 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
if (!silent)
showWarning(tr("This address does not exist","The DNS gives the Tox ID associated to toxme.se addresses"));
return result;
return QByteArray();
}
else if (dns.error() != QDnsLookup::NoError)
{
if (!silent)
showWarning(tr("Error while looking up DNS","The DNS gives the Tox ID associated to toxme.se addresses"));
return result;
return QByteArray();
}
const QList<QDnsTextRecord> textRecords = dns.textRecords();
@ -95,7 +115,7 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
if (!silent)
showWarning(tr("No text record found", "Error with the DNS"));
return result;
return QByteArray();
}
const QList<QByteArray> textRecordValues = textRecords.last().values();
@ -104,13 +124,20 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
if (!silent)
showWarning(tr("Unexpected number of values in text record", "Error with the DNS"));
return result;
return QByteArray();
}
result = textRecordValues.first();
return result;
return textRecordValues.first();
}
/**
@brief Send query to DNS to find Tox Id.
@note Will *NOT* fallback on queryTox1 anymore.
@param server Server to sending query.
@param record Should look like user@domain.tld.
@param silent If true, there will be no output on error.
@return Tox Id string.
*/
QString ToxDNS::queryTox3(const tox3_server& server, const QString &record, bool silent)
{
QByteArray nameData = record.left(record.indexOf('@')).toUtf8(), id, realRecord;
@ -190,44 +217,40 @@ fallbackOnTox1:
return toxIdStr;
}
/**
@brief Tries to map a text string to a ToxId struct, will query Tox DNS records if necessary.
@param address Adress to search for Tox ID.
@param silent If true, there will be no output on error.
@return Found Tox Id.
*/
ToxId ToxDNS::resolveToxAddress(const QString &address, bool silent)
{
ToxId toxId;
if (address.isEmpty())
{
return toxId;
}
else if (ToxId::isToxId(address))
{
toxId = ToxId(address);
return toxId;
}
else
{
// If we're querying one of our pinned server, do a toxdns3 request directly
QString servname = address.mid(address.indexOf('@')+1);
for (const ToxDNS::tox3_server& pin : ToxDNS::pinnedServers)
{
if (servname == pin.name)
{
toxId = ToxId(queryTox3(pin, address, silent));
return toxId;
}
}
return ToxId();
// Otherwise try toxdns3 if we can get a pubkey or fallback to toxdns1
QByteArray pubkey = fetchLastTextRecord("_tox."+servname, true);
if (!pubkey.isEmpty())
{
pubkey = QByteArray::fromHex(pubkey);
if (ToxId::isToxId(address))
return ToxId(address);
QByteArray servnameData = servname.toUtf8();
ToxDNS::tox3_server server;
server.name = servnameData.data();
server.pubkey = (uint8_t*)pubkey.data();
toxId = ToxId(queryTox3(server, address, silent));
}
return toxId;
// If we're querying one of our pinned servers, do a toxdns3 request directly
QString servname = address.mid(address.indexOf('@')+1);
for (const ToxDNS::tox3_server& pin : ToxDNS::pinnedServers)
{
if (servname == pin.name)
return ToxId(queryTox3(pin, address, silent));
}
// Otherwise try toxdns3 if we can get a pubkey or fallback to toxdns1
QByteArray pubkey = fetchLastTextRecord("_tox."+servname, true);
if (!pubkey.isEmpty())
{
pubkey = QByteArray::fromHex(pubkey);
QByteArray servnameData = servname.toUtf8();
ToxDNS::tox3_server server;
server.name = servnameData.data();
server.pubkey = (uint8_t*)pubkey.data();
return ToxId(queryTox3(server, address, silent));
}
return ToxId();
}

View File

@ -26,34 +26,29 @@
#include <QDnsLookup>
#include <QObject>
/// Handles tox1 and tox3 DNS queries
class ToxDNS : public QObject
{
Q_OBJECT
public:
struct tox3_server ///< Represents a tox3 server
struct tox3_server
{
tox3_server()=default;
tox3_server(const char* _name, uint8_t _pk[32]):name{_name},pubkey{_pk}{}
const char* name; ///< Hostname of the server, e.g. toxme.se
uint8_t* pubkey; ///< Public key of the tox3 server, usually 256bit long
const char* name;
uint8_t* pubkey;
};
public:
/// Tries to map a text string to a ToxId struct, will query Tox DNS records if necessary
static ToxId resolveToxAddress(const QString& address, bool silent=true);
static QString queryTox3(const tox3_server& server, const QString& record, bool silent=true); ///< Record should look like user@domain.tld, will *NOT* fallback on queryTox1 anymore
static QString queryTox3(const tox3_server& server, const QString& record, bool silent=true);
protected:
static void showWarning(const QString& message);
ToxDNS()=default;
private:
/// Try to fetch the first entry of the given TXT record
/// Returns an empty object on failure. May block for up to ~3s
/// May display message boxes on error if silent if false
static QByteArray fetchLastTextRecord(const QString& record, bool silent=true);
public:

View File

@ -30,6 +30,14 @@
#include <string>
#include <ctime>
/**
@class Toxme
@brief This class implements a client for the toxme.se API
@note The class is thread safe
@note May process events while waiting for blocking calls
*/
QByteArray Toxme::makeJsonRequest(QString url, QString json, QNetworkReply::NetworkError &error)
{
if (error)
@ -136,6 +144,11 @@ QByteArray Toxme::prepareEncryptedJson(QString url, int action, QString payload)
return json.toUtf8();
}
/**
@brief Converts a toxme address to a Tox ID.
@param address Toxme address.
@return Found ToxId (an empty ID on error).
*/
ToxId Toxme::lookup(QString address)
{
// JSON injection ?
@ -204,6 +217,16 @@ Toxme::ExecCode Toxme::extractError(QString json)
return ExecCode(r);
}
/**
@brief Creates a new toxme address associated with a Tox ID.
@param[out] code Tox error code @see getErrorMessage.
@param[in] server Create toxme account on this server.
@param[in] id ToxId of current user.
@param[in] address Create toxme account with this adress.
@param[in] keepPrivate If true, the address will not be published on toxme site.
@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,
bool keepPrivate, QString bio)
{
@ -271,6 +294,12 @@ QString Toxme::getPass(QString json, ExecCode &code) {
return json;
}
/**
@brief Deletes the address associated with your current Tox ID.
@param server Server to delete the address from.
@param id ToxId to delete.
@return Status code returned from server.
*/
Toxme::ExecCode Toxme::deleteAddress(QString server, ToxId id)
{
const QString payload{"{\"public_key\":\""+id.toString().left(64)+"\","
@ -289,10 +318,10 @@ Toxme::ExecCode Toxme::deleteAddress(QString server, ToxId id)
}
/**
* @brief Return string of the corresponding error code
* @param errorCode Code to get error message
* @return Source error message
*/
@brief Return string of the corresponding error code
@param errorCode Code to get error message
@return Source error message
*/
QString Toxme::getErrorMessage(int errorCode)
{
switch (errorCode) {
@ -336,10 +365,10 @@ QString Toxme::getErrorMessage(int errorCode)
}
/**
* @brief Return translated error message
* @param errorCode Code to translate
* @return Translated Toxme error message
*/
@brief Return translated error message
@param errorCode Code to translate
@return Translated Toxme error message
*/
QString Toxme::translateErrorMessage(int errorCode)
{
switch (errorCode) {

View File

@ -30,9 +30,6 @@
class QNetworkAccessManager;
/// This class implements a client for the toxme.se API
/// The class is thread safe
/// May process events while waiting for blocking calls
class Toxme
{
public:
@ -45,22 +42,15 @@ public:
NoPassword = 4
};
/// Converts a toxme.se address to a Tox ID, returns an empty ID on error
static ToxId lookup(QString address);
/// Creates a new toxme.se address associated with a Tox ID.
/// If keepPrivate, the address will not be published on toxme.se
/// The bio is a short optional description of yourself if you want to publish your address.
/// If it passed without error, return password, else return errorCode in QString
static QString createAddress(ExecCode &code, QString server, ToxId id, QString address,
bool keepPrivate=true, QString bio=QString());
/// Deletes the address associated with your current Tox ID
static ExecCode deleteAddress(QString server, ToxId id);
/// Return string of the corresponding error code
static QString getErrorMessage(int errorCode);
static QString translateErrorMessage(int errorCode);
private:
Toxme()=delete;
Toxme() = delete;
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);

View File

@ -44,6 +44,12 @@ bool toxURIEventHandler(const QByteArray& eventData)
return true;
}
/**
@brief Shows a dialog asking whether or not to add this tox address as a friend.
@note Will wait until the core is ready first.
@param toxURI Tox URI to try to add.
@return True, if tox URI is correct, false otherwise.
*/
bool handleToxURI(const QString &toxURI)
{
Core* core = Core::getInstance();
@ -75,6 +81,7 @@ bool handleToxURI(const QString &toxURI)
.arg(Nexus::getCore()->getUsername()));
if (dialog.exec() == QDialog::Accepted)
Core::getInstance()->requestFriendship(toxId, dialog.getRequestMessage());
return true;
}

View File

@ -23,8 +23,6 @@
#include <QDialog>
/// Shows a dialog asking whether or not to add this tox address as a friend
/// Will wait until the core is ready first
bool handleToxURI(const QString& toxURI);
// Internals

View File

@ -43,6 +43,14 @@
#include <QSignalMapper>
#endif
/**
@class Nexus
This class is in charge of connecting various systems together
and forwarding signals appropriately to the right objects,
it is in charge of starting the GUI and the Core.
*/
Q_DECLARE_OPAQUE_POINTER(ToxAV*)
static Nexus* nexus{nullptr};
@ -66,6 +74,11 @@ Nexus::~Nexus()
#endif
}
/**
Sets up invariants and calls showLogin
Hides the login screen and shows the GUI for the given profile.
Will delete the current GUI, if it exists.
*/
void Nexus::start()
{
qDebug() << "Starting up";
@ -131,6 +144,9 @@ void Nexus::start()
showLogin();
}
/**
@brief Hides the main GUI, delete the profile, and shows the login screen
*/
void Nexus::showLogin()
{
delete widget;
@ -201,6 +217,9 @@ void Nexus::showMainGUI()
profile->startCore();
}
/**
@brief Returns the singleton instance.
*/
Nexus& Nexus::getInstance()
{
if (!nexus)
@ -215,6 +234,10 @@ void Nexus::destroyInstance()
nexus = nullptr;
}
/**
@brief Get core instance.
@return nullptr if not started, core instance otherwise.
*/
Core* Nexus::getCore()
{
Nexus& nexus = getInstance();
@ -224,11 +247,19 @@ Core* Nexus::getCore()
return nexus.profile->getCore();
}
/**
@brief Get current user profile.
@return nullptr if not started, profile otherwise.
*/
Profile* Nexus::getProfile()
{
return getInstance().profile;
}
/**
@brief Unload the current profile, if any, and replaces it.
@param profile Profile to set.
*/
void Nexus::setProfile(Profile* profile)
{
getInstance().profile = profile;
@ -236,6 +267,10 @@ void Nexus::setProfile(Profile* profile)
Settings::getInstance().loadPersonal(profile);
}
/**
@brief Get desktop GUI widget.
@return nullptr if not started, desktop widget otherwise.
*/
Widget* Nexus::getDesktopGUI()
{
return getInstance().widget;
@ -250,6 +285,11 @@ QString Nexus::getSupportedImageFilter()
return tr("Images (%1)", "filetype filter").arg(res.left(res.size()-1));
}
/**
@brief Dangerous way to find out if a path is writable.
@param filepath Path to file which should be deleted.
@return True, if file writeable, false otherwise.
*/
bool Nexus::tryRemoveFile(const QString& filepath)
{
QFile tmp(filepath);
@ -258,6 +298,9 @@ bool Nexus::tryRemoveFile(const QString& filepath)
return writable;
}
/**
@brief Calls showLogin asynchronously, so we can safely logout from within the main GUI
*/
void Nexus::showLoginLater()
{
GUI::setEnabled(false);

View File

@ -37,30 +37,25 @@ class QActionGroup;
class QSignalMapper;
#endif
/// This class is in charge of connecting various systems together
/// and forwarding signals appropriately to the right objects
/// It is in charge of starting the GUI and the Core
class Nexus : public QObject
{
Q_OBJECT
public:
void start(); ///< Sets up invariants and calls showLogin
/// Hides the login screen and shows the GUI for the given profile.
/// Will delete the current GUI, if it exists.
void start();
void showMainGUI();
static Nexus& getInstance();
static void destroyInstance();
static Core* getCore(); ///< Will return 0 if not started
static Profile* getProfile(); ///< Will return 0 if not started
static void setProfile(Profile* profile); ///< Delete the current profile, if any, and replaces it
static Widget* getDesktopGUI(); ///< Will return 0 if not started
static Core* getCore();
static Profile* getProfile();
static void setProfile(Profile* profile);
static Widget* getDesktopGUI();
static QString getSupportedImageFilter();
static bool tryRemoveFile(const QString& filepath); ///< Dangerous way to find out if a path is writable
static bool tryRemoveFile(const QString& filepath);
public slots:
void showLogin(); ///< Hides the man GUI, delete the profile, and shows the login screen
void showLoginLater(); ///< Calls showLogin asynchronously, so we can safely logout from within the main GUI
void showLogin();
void showLoginLater();
#ifdef Q_OS_MAC
public:

View File

@ -29,6 +29,11 @@
#include <QDebug>
#include <QSqlError>
/**
@var static TOX_PASS_KEY EncryptedDb::decryptionKey
@note When importing, the decryption key may not be the same as the profile key
*/
qint64 EncryptedDb::encryptedChunkSize = 4096;
qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - TOX_PASS_ENCRYPTION_EXTRA_LENGTH;

View File

@ -46,7 +46,7 @@ private:
static qint64 plainChunkSize;
static qint64 encryptedChunkSize;
static TOX_PASS_KEY decryptionKey; ///< When importing, the decryption key may not be the same as the profile key
static TOX_PASS_KEY decryptionKey;
qint64 chunkPosition;
QByteArray buffer;

View File

@ -14,6 +14,60 @@
#include <sqlcipher/sqlite3.h>
/**
@class RawDatabase
@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.
@var QMutex RawDatabase::transactionsMutex;
@brief Protects pendingTransactions
*/
/**
@class Query
@brief A query to be executed by the database.
Can be composed of one or more SQL statements in the query,
optional BLOB parameters to be bound, and callbacks fired when the query is executed
Calling any database method from a query callback is undefined behavior.
@var QByteArray RawDatabase::Query::query
@brief UTF-8 query string
@var QVector<QByteArray> RawDatabase::Query::blobs
@brief Bound data blobs
@var std::function<void(int64_t)> RawDatabase::Query::insertCallback
@brief Called after execution with the last insert rowid
@var std::function<void(const QVector<QVariant>&)> RawDatabase::Query::rowCallback
@brief Called during execution for each row
@var QVector<sqlite3_stmt*> RawDatabase::Query::statements
@brief Statements to be compiled from the query
*/
/**
@struct Transaction
@brief SQL transactions to be processed.
A transaction is made of queries, which can have bound BLOBs.
@var std::atomic_bool* RawDatabase::Transaction::success = nullptr;
@brief If not a nullptr, the result of the transaction will be set
@var std::atomic_bool* RawDatabase::Transaction::done = nullptr;
@brief If not a nullptr, will be set to true when the transaction has been executed
*/
/**
@brief Tries to open a database.
@param path Path to database.
@param password If empty, the database will be opened unencrypted.
Otherwise we will use toxencryptsave to derive a key and encrypt the database.
*/
RawDatabase::RawDatabase(const QString &path, const QString& password)
: workerThread{new QThread}, path{path}, currentHexKey{deriveKey(password)}
{
@ -33,6 +87,12 @@ RawDatabase::~RawDatabase()
workerThread->wait(50);
}
/**
@brief Tries to open the database with the given (possibly empty) key.
@param path Path to database.
@param hexKey Hex representation of the key in string.
@return True if success, false otherwise.
*/
bool RawDatabase::open(const QString& path, const QString &hexKey)
{
if (QThread::currentThread() != workerThread.get())
@ -75,6 +135,9 @@ bool RawDatabase::open(const QString& path, const QString &hexKey)
return true;
}
/**
@brief Close the database and free its associated resources.
*/
void RawDatabase::close()
{
if (QThread::currentThread() != workerThread.get())
@ -89,22 +152,41 @@ void RawDatabase::close()
qWarning() << "Error closing database:"<<sqlite3_errmsg(sqlite);
}
/**
@brief Checks, that the database is open.
@return True if the database was opened successfully.
*/
bool RawDatabase::isOpen()
{
// We don't need thread safety since only the ctor/dtor can write this pointer
return sqlite != nullptr;
}
/**
@brief Executes a SQL transaction synchronously.
@param statement Statement to execute.
@return Whether the transaction was successful.
*/
bool RawDatabase::execNow(const QString& statement)
{
return execNow(Query{statement});
}
/**
@brief Executes a SQL transaction synchronously.
@param statement Statement to execute.
@return Whether the transaction was successful.
*/
bool RawDatabase::execNow(const RawDatabase::Query &statement)
{
return execNow(QVector<Query>{statement});
}
/**
@brief Executes a SQL transaction synchronously.
@param statements List of statements to execute.
@return Whether the transaction was successful.
*/
bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements)
{
if (!sqlite)
@ -134,6 +216,10 @@ bool RawDatabase::execNow(const QVector<RawDatabase::Query> &statements)
return success.load(std::memory_order_acquire);
}
/**
@brief Executes a SQL transaction asynchronously.
@param statement Statement to execute.
*/
void RawDatabase::execLater(const QString &statement)
{
execLater(Query{statement});
@ -162,11 +248,20 @@ void RawDatabase::execLater(const QVector<RawDatabase::Query> &statements)
QMetaObject::invokeMethod(this, "process");
}
/**
@brief Waits until all the pending transactions are executed.
*/
void RawDatabase::sync()
{
QMetaObject::invokeMethod(this, "process", Qt::BlockingQueuedConnection);
}
/**
@brief Changes the database password, encrypting or decrypting if necessary.
@param password If password is empty, the database will be decrypted.
@return True if success, false otherwise.
@note Will process all transactions before changing the password.
*/
bool RawDatabase::setPassword(const QString& password)
{
if (!sqlite)
@ -260,6 +355,13 @@ bool RawDatabase::setPassword(const QString& password)
return true;
}
/**
@brief Moves the database file on disk to match the new path.
@param newPath Path to move database file.
@return True if success, false otherwise.
@note Will process all transactions before renaming
*/
bool RawDatabase::rename(const QString &newPath)
{
if (!sqlite)
@ -291,6 +393,11 @@ bool RawDatabase::rename(const QString &newPath)
return open(path, currentHexKey);
}
/**
@brief Deletes the on disk database file after closing it.
@note Will process all transactions before deletings.
@return True if success, false otherwise.
*/
bool RawDatabase::remove()
{
if (!sqlite)
@ -311,7 +418,11 @@ bool RawDatabase::remove()
return QFile::remove(path);
}
/**
@brief Derives a 256bit key from the password and returns it hex-encoded
@param password Password to decrypt database
@return String representation of key
*/
QString RawDatabase::deriveKey(const QString &password)
{
if (password.isEmpty())
@ -327,6 +438,12 @@ QString RawDatabase::deriveKey(const QString &password)
return QByteArray((char*)key.key, 32).toHex();
}
/**
@brief Implements the actual processing of pending transactions.
Unqueues, compiles, binds and executes queries, then notifies of results
@warning MUST only be called from the worker thread
*/
void RawDatabase::process()
{
assert(QThread::currentThread() == workerThread.get());
@ -460,6 +577,12 @@ void RawDatabase::process()
}
}
/**
@brief Extracts a variant from one column of a result row depending on the column type.
@param stmt Statement to execute.
@param col Number of column to extract.
@return Extracted data.
*/
QVariant RawDatabase::extractData(sqlite3_stmt *stmt, int col)
{
int type = sqlite3_column_type(stmt, col);

View File

@ -15,17 +15,11 @@
struct sqlite3;
struct sqlite3_stmt;
/// 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
class RawDatabase : QObject
{
Q_OBJECT
public:
/// A query to be executed by the database. Can be composed of one or more SQL statements in the query,
/// optional BLOB parameters to be bound, and callbacks fired when the query is executed
/// Calling any database method from a query callback is undefined behavior
class Query
{
public:
@ -37,71 +31,49 @@ public:
: query{query.toUtf8()}, rowCallback{rowCallback} {}
Query() = default;
private:
QByteArray query; ///< UTF-8 query string
QVector<QByteArray> blobs; ///< Bound data blobs
std::function<void(int64_t)> insertCallback; ///< Called after execution with the last insert rowid
std::function<void(const QVector<QVariant>&)> rowCallback; ///< Called during execution for each row
QVector<sqlite3_stmt*> statements; ///< Statements to be compiled from the query
QByteArray query;
QVector<QByteArray> blobs;
std::function<void(int64_t)> insertCallback;
std::function<void(const QVector<QVariant>&)> rowCallback;
QVector<sqlite3_stmt*> statements;
friend class RawDatabase;
};
public:
/// Tries to open a database
/// If password is empty, the database will be opened unencrypted
/// Otherwise we will use toxencryptsave to derive a key and encrypt the database
RawDatabase(const QString& path, const QString& password);
~RawDatabase();
bool isOpen(); ///< Returns true if the database was opened successfully
/// Executes a SQL transaction synchronously.
/// Returns whether the transaction was successful.
bool isOpen();
bool execNow(const QString& statement);
bool execNow(const Query& statement);
bool execNow(const QVector<Query>& statements);
/// Executes a SQL transaction asynchronously.
void execLater(const QString& statement);
void execLater(const Query& statement);
void execLater(const QVector<Query>& statements);
/// Waits until all the pending transactions are executed
void sync();
public slots:
/// Changes the database password, encrypting or decrypting if necessary
/// If password is empty, the database will be decrypted
/// Will process all transactions before changing the password
bool setPassword(const QString& password);
/// Moves the database file on disk to match the new path
/// Will process all transactions before renaming
bool rename(const QString& newPath);
/// Deletes the on disk database file after closing it
/// Will process all transactions before deletings
bool remove();
protected slots:
/// Tries to open the database with the given (possibly empty) key
bool open(const QString& path, const QString& hexKey = {});
/// Closes the database and free its associated resources
void close();
/// Implements the actual processing of pending transactions
/// Unqueues, compiles, binds and executes queries, then notifies of results
/// MUST only be called from the worker thread
void process();
protected:
/// Derives a 256bit key from the password and returns it hex-encoded
static QString deriveKey(const QString &password);
/// Extracts a variant from one column of a result row depending on the column type
static QVariant extractData(sqlite3_stmt* stmt, int col);
private:
/// SQL transactions to be processed
/// A transaction is made of queries, which can have bound BLOBs
struct Transaction
{
QVector<Query> queries;
/// If not a nullptr, the result of the transaction will be set
std::atomic_bool* success = nullptr;
/// If not a nullptr, will be set to true when the transaction has been executed
std::atomic_bool* done = nullptr;
};
@ -109,7 +81,6 @@ private:
sqlite3* sqlite;
std::unique_ptr<QThread> workerThread;
QQueue<Transaction> pendingTransactions;
/// Protects pendingTransactions
QMutex transactionsMutex;
QString path;
QString currentHexKey;

View File

@ -8,12 +8,32 @@
using namespace std;
/**
@class History
@brief Interacts with the profile database to save the chat history.
@var QHash<QString, int64_t> History::peers
@brief Maps friend public keys to unique IDs by index.
Caches mappings to speed up message saving.
*/
/**
@brief Opens the profile database and prepares to work with the history.
@param profileName Profile name to load.
@param password If empty, the database will be opened unencrypted.
*/
History::History(const QString &profileName, const QString &password)
: db{getDbPath(profileName), password}
{
init();
}
/**
@brief Opens the profile database, and import from the old database.
@param profileName Profile name to load.
@param password If empty, the database will be opened unencrypted.
@param oldHistory Old history to import.
*/
History::History(const QString &profileName, const QString &password, const HistoryKeeper &oldHistory)
: History{profileName, password}
{
@ -27,26 +47,45 @@ History::~History()
db.sync();
}
/**
@brief Checks if the database was opened successfully
@return True if database if opened, false otherwise.
*/
bool History::isValid()
{
return db.isOpen();
}
/**
@brief Changes the database password, will encrypt or decrypt if necessary.
@param password Password to set.
*/
void History::setPassword(const QString& password)
{
db.setPassword(password);
}
/**
@brief Moves the database file on disk to match the new name.
@param newName New name.
*/
void History::rename(const QString &newName)
{
db.rename(getDbPath(newName));
}
/**
@brief Deletes the on-disk database file.
@return True if success, false otherwise.
*/
bool History::remove()
{
return db.remove();
}
/**
@brief Erases all the chat history from the database.
*/
void History::eraseHistory()
{
db.execNow("DELETE FROM faux_offline_pending;"
@ -56,6 +95,10 @@ void History::eraseHistory()
"VACUUM;");
}
/**
@brief Erases the chat history with one friend.
@param friendPk Friend public key to erase.
*/
void History::removeFriendHistory(const QString &friendPk)
{
if (!peers.contains(friendPk))
@ -81,6 +124,16 @@ void History::removeFriendHistory(const QString &friendPk)
}
}
/**
@brief Generate query to insert new message in database
@param friendPk Friend publick key to save.
@param message Message to save.
@param sender Sender to save.
@param time Time of message sending.
@param isSent True if message was already sent.
@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)
@ -139,12 +192,29 @@ QVector<RawDatabase::Query> History::generateNewMessageQueries(const QString &fr
return queries;
}
/**
@brief Saves a chat message in the database.
@param friendPk Friend publick key to save.
@param message Message to save.
@param sender Sender to save.
@param time Time of message sending.
@param isSent True if message was already sent.
@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, std::function<void(int64_t)> insertIdCallback)
{
db.execLater(generateNewMessageQueries(friendPk, message, sender, time, isSent, dispName, insertIdCallback));
}
/**
@brief Fetches chat messages from the database.
@param friendPk Friend publick key to fetch.
@param from Start of period to fetch.
@param to End of period to fetch.
@return List of messages.
*/
QList<History::HistMessage> History::getChatHistory(const QString &friendPk, const QDateTime &from, const QDateTime &to)
{
QList<HistMessage> messages;
@ -174,16 +244,30 @@ QList<History::HistMessage> History::getChatHistory(const QString &friendPk, con
return messages;
}
/**
@brief Marks a message as sent.
Removing message from the faux-offline pending messages list.
@param id Message ID.
*/
void History::markAsSent(qint64 id)
{
db.execLater(QString("DELETE FROM faux_offline_pending WHERE id=%1;").arg(id));
}
/**
@brief Retrieves the path to the database file for a given profile.
@param profileName Profile name.
@return Path to database.
*/
QString History::getDbPath(const QString &profileName)
{
return Settings::getInstance().getSettingsDirPath() + profileName + ".db";
}
/**
@brief Makes sure the history tables are created
*/
void History::init()
{
if (!isValid())
@ -207,6 +291,10 @@ void History::init()
}});
}
/**
@brief Imports messages from the old history file.
@param oldHistory Old history to import.
*/
void History::import(const HistoryKeeper &oldHistory)
{
if (!isValid())

View File

@ -12,7 +12,6 @@ class Profile;
class HistoryKeeper;
class RawDatabase;
/// Interacts with the profile database to save the chat history
class History
{
public:
@ -31,40 +30,26 @@ public:
};
public:
/// Opens the profile database and prepares to work with the history
/// If password is empty, the database will be opened unencrypted
History(const QString& profileName, const QString& password);
/// Opens the profile database, and import from the old database
/// If password is empty, the database will be opened unencrypted
History(const QString& profileName, const QString& password, const HistoryKeeper& oldHistory);
~History();
/// Checks if the database was opened successfully
bool isValid();
/// Imports messages from the old history file
void import(const HistoryKeeper& oldHistory);
/// Changes the database password, will encrypt or decrypt if necessary
void setPassword(const QString& password);
/// Moves the database file on disk to match the new name
void rename(const QString& newName);
/// Deletes the on-disk database file
bool remove();
/// Erases all the chat history from the database
void eraseHistory();
/// Erases the chat history with one friend
void removeFriendHistory(const QString& friendPk);
/// Saves a chat message in the database
void addNewMessage(const QString& friendPk, const QString& message, const QString& sender,
const QDateTime &time, bool isSent, QString dispName,
std::function<void(int64_t)> insertIdCallback={});
/// Fetches chat messages from the database
QList<HistMessage> getChatHistory(const QString& friendPk, const QDateTime &from, const QDateTime &to);
/// Marks a message as sent, removing it from the faux-offline pending messages list
void markAsSent(qint64 id);
/// Retrieves the path to the database file for a given profile.
static QString getDbPath(const QString& profileName);
protected:
/// Makes sure the history tables are created
void init();
QVector<RawDatabase::Query> generateNewMessageQueries(const QString& friendPk, const QString& message,
const QString& sender, const QDateTime &time, bool isSent, QString dispName,
@ -72,8 +57,7 @@ protected:
private:
RawDatabase db;
// Cached mappings to speed up message saving
QHash<QString, int64_t> peers; ///< Maps friend public keys to unique IDs by index
QHash<QString, int64_t> peers;
};
#endif // HISTORY_H

View File

@ -35,9 +35,19 @@
#include "src/persistence/db/plaindb.h"
#include "src/persistence/db/encrypteddb.h"
/**
@class HistoryKeeper
@brief THIS IS A LEGACY CLASS KEPT FOR BACKWARDS COMPATIBILITY
@deprecated See the History class instead
@warning DO NOT USE!
*/
static HistoryKeeper *historyInstance = nullptr;
QMutex HistoryKeeper::historyMutex;
/**
@brief Returns the singleton instance.
*/
HistoryKeeper *HistoryKeeper::getInstance(const Profile& profile)
{
historyMutex.lock();

View File

@ -27,12 +27,6 @@
#include <QMutex>
#include <tox/toxencryptsave.h>
/**
* THIS IS A LEGACY CLASS KEPT FOR BACKWARDS COMPATIBILITY
* DO NOT USE!
* See the History class instead
*/
class Profile;
class GenericDdInterface;
namespace Db { enum class syncType; }

View File

@ -26,6 +26,15 @@
#include <QMutexLocker>
#include <QTimer>
/**
@var static const int OfflineMsgEngine::offlineTimeout
@brief timeout after which faux offline messages get to be re-sent.
Originally was 2s, but since that was causing lots of duplicated
messages on receiving end, make qTox be more lazy about re-sending
should be 20s.
*/
const int OfflineMsgEngine::offlineTimeout = 20000;
QMutex OfflineMsgEngine::globalMutex;

View File

@ -57,10 +57,6 @@ private:
QHash<int, int64_t> receipts;
QMap<int64_t, MsgPtr> undeliveredMsgs;
// timeout after which faux offline messages get to be re-sent
// originally was 2s, but since that was causing lots of duplicated
// messages on receiving end, make qTox be more lazy about re-sending
// should be 20s
static const int offlineTimeout;
};

View File

@ -35,6 +35,21 @@
#include <QDebug>
#include <sodium.h>
/**
@class Profile
@brief Manages user profiles.
@var bool Profile::newProfile
@brief True if this is a newly created profile, with no .tox save file yet.
@var bool Profile::isRemoved
@brief True if the profile has been removed by remove().
@var static constexpr int Profile::encryptHeaderSize = 8
@brief How much data we need to read to check if the file is encrypted.
@note Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined.
*/
QVector<QString> Profile::profiles;
Profile::Profile(QString name, const QString &password, bool isNewProfile)
@ -65,6 +80,14 @@ Profile::Profile(QString name, const QString &password, bool isNewProfile)
QObject::connect(coreThread, &QThread::started, core, &Core::start);
}
/**
@brief Locks and loads an existing profile and creates the associate Core* instance.
@param name Profile name.
@param password Profile password.
@return Returns a nullptr on error. Profile pointer otherwise.
@example If the profile is already in use return nullptr.
*/
Profile* Profile::loadProfile(QString name, const QString &password)
{
if (ProfileLocker::hasLock())
@ -142,6 +165,14 @@ Profile* Profile::loadProfile(QString name, const QString &password)
return p;
}
/**
@brief Creates a new profile and the associated Core* instance.
@param name Username.
@param password If password is not empty, the profile will be encrypted.
@return Returns a nullptr on error. Profile pointer otherwise.
@example If the profile is already in use return nullptr.
*/
Profile* Profile::createProfile(QString name, QString password)
{
if (ProfileLocker::hasLock())
@ -182,6 +213,11 @@ Profile::~Profile()
}
}
/**
@brief Lists all the files in the config dir with a given extension
@param extension Raw extension, e.g. "jpeg" not ".jpeg".
@return Vector of filenames.
*/
QVector<QString> Profile::getFilesByExt(QString extension)
{
QDir dir(Settings::getInstance().getSettingsDirPath());
@ -195,6 +231,10 @@ QVector<QString> Profile::getFilesByExt(QString extension)
return out;
}
/**
@brief Scan for profile, automatically importing them if needed.
@warning NOT thread-safe.
*/
void Profile::scanProfiles()
{
profiles.clear();
@ -222,6 +262,9 @@ QString Profile::getName() const
return name;
}
/**
@brief Starts the Core thread
*/
void Profile::startCore()
{
coreThread->start();
@ -232,6 +275,10 @@ bool Profile::isNewProfile()
return newProfile;
}
/**
@brief Loads the profile's .tox save from file, unencrypted
@return Byte array of loaded profile save.
*/
QByteArray Profile::loadToxSave()
{
assert(!isRemoved);
@ -290,6 +337,10 @@ fail:
return data;
}
/**
@brief Saves the profile's .tox save, encrypted if needed.
@warning Invalid on deleted profiles.
*/
void Profile::saveToxSave()
{
assert(core->isReady());
@ -298,6 +349,11 @@ void Profile::saveToxSave()
saveToxSave(data);
}
/**
@brief Write the .tox save, encrypted if needed.
@param data Byte array of profile save.
@warning Invalid on deleted profiles.
*/
void Profile::saveToxSave(QByteArray data)
{
assert(!isRemoved);
@ -340,6 +396,12 @@ void Profile::saveToxSave(QByteArray data)
}
}
/**
@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.
@return Path to the avatar.
*/
QString Profile::avatarPath(const QString &ownerId, bool forceUnencrypted)
{
if (password.isEmpty() || forceUnencrypted)
@ -357,11 +419,20 @@ QString Profile::avatarPath(const QString &ownerId, bool forceUnencrypted)
return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() + ".png";
}
/**
@brief Get our avatar from cache.
@return Avatar as QPixmap.
*/
QPixmap Profile::loadAvatar()
{
return loadAvatar(core->getSelfId().publicKey);
}
/**
@brief Get a contact's avatar from cache.
@param ownerId Friend ID to load avatar.
@return Avatar as QPixmap.
*/
QPixmap Profile::loadAvatar(const QString &ownerId)
{
QPixmap pic;
@ -369,11 +440,22 @@ QPixmap Profile::loadAvatar(const QString &ownerId)
return pic;
}
/**
@brief Get a contact's avatar from cache
@param ownerId Friend ID to load avatar.
@return Avatar as QByteArray.
*/
QByteArray Profile::loadAvatarData(const QString &ownerId)
{
return loadAvatarData(ownerId, password);
}
/**
@brief Get a contact's avatar from cache, with a specified profile password.
@param ownerId Friend ID to load avatar.
@param password Profile password to decrypt data.
@return Avatar as QByteArray.
*/
QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &password)
{
QString path = avatarPath(ownerId);
@ -401,6 +483,11 @@ QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &passwo
return pic;
}
/**
@brief Save an avatar to cache.
@param pic Picture to save.
@param ownerId ID of avatar owner.
*/
void Profile::saveAvatar(QByteArray pic, const QString &ownerId)
{
if (!password.isEmpty() && !pic.isEmpty())
@ -425,6 +512,11 @@ void Profile::saveAvatar(QByteArray pic, const QString &ownerId)
}
}
/**
@brief Get the tox hash of a cached avatar.
@param ownerId Friend ID to get hash.
@return Avatar tox hash.
*/
QByteArray Profile::getAvatarHash(const QString &ownerId)
{
QByteArray pic = loadAvatarData(ownerId);
@ -433,21 +525,36 @@ QByteArray Profile::getAvatarHash(const QString &ownerId)
return avatarHash;
}
/**
@brief Removes our own avatar.
*/
void Profile::removeAvatar()
{
removeAvatar(core->getSelfId().publicKey);
}
/**
@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()
{
return Settings::getInstance().getEnableLogging() && history;
}
/**
@brief Get chat history.
@return May return a nullptr if the history failed to load.
*/
History *Profile::getHistory()
{
return history.get();
}
/**
@brief Removes a cached avatar.
@param ownerId Friend ID whose avater to delete.
*/
void Profile::removeAvatar(const QString &ownerId)
{
QFile::remove(avatarPath(ownerId));
@ -461,11 +568,21 @@ bool Profile::exists(QString name)
return QFile::exists(path+".tox");
}
/**
@brief Checks, if profile has a password.
@return True if we have a password set (doesn't check the actual file on disk).
*/
bool Profile::isEncrypted() const
{
return !password.isEmpty();
}
/**
@brief Checks if profile is encrypted.
@note Checks the actual file on disk.
@param name Profile name.
@return True if profile is encrypted, false otherwise.
*/
bool Profile::isEncrypted(QString name)
{
uint8_t data[encryptHeaderSize] = {0};
@ -483,6 +600,12 @@ bool Profile::isEncrypted(QString name)
return tox_is_data_encrypted(data);
}
/**
@brief Removes the profile permanently.
Updates the profiles vector.
@return Vector of filenames that could not be removed.
@warning It is invalid to call loadToxSave or saveToxSave on a deleted profile.
*/
QVector<QString> Profile::remove()
{
if (isRemoved)
@ -546,6 +669,11 @@ QVector<QString> Profile::remove()
return ret;
}
/**
@brief Tries to rename the profile.
@param newName New name for the profile.
@return False on error, true otherwise.
*/
bool Profile::rename(QString newName)
{
QString path = Settings::getInstance().getSettingsDirPath() + name,
@ -568,6 +696,10 @@ bool Profile::rename(QString newName)
return true;
}
/**
@brief Checks whether the password is valid.
@return True, if password is valid, false otherwise.
*/
bool Profile::checkPassword()
{
if (isRemoved)
@ -586,6 +718,9 @@ const TOX_PASS_KEY& Profile::getPasskey() const
return passkey;
}
/**
@brief Delete core and restart a new one
*/
void Profile::restartCore()
{
GUI::setEnabled(false); // Core::reset re-enables it
@ -594,6 +729,10 @@ void Profile::restartCore()
QMetaObject::invokeMethod(core, "reset");
}
/**
@brief Changes the encryption password and re-saves everything with it
@param newPassword Password for encryption.
*/
void Profile::setPassword(const QString &newPassword)
{
QByteArray avatar = loadAvatarData(core->getSelfId().publicKey);

View File

@ -32,76 +32,54 @@
class Core;
class QThread;
/// Manages user profiles
class Profile
{
public:
/// Locks and loads an existing profile and create the associate Core* instance
/// Returns a nullptr on error, for example if the profile is already in use
static Profile* loadProfile(QString name, const QString &password = QString());
/// Creates a new profile and the associated Core* instance
/// If password is not empty, the profile will be encrypted
/// Returns a nullptr on error, for example if the profile already exists
static Profile* createProfile(QString name, QString password);
~Profile();
Core* getCore();
QString getName() const;
void startCore(); ///< Starts the Core thread
void restartCore(); ///< Delete core and restart a new one
void startCore();
void restartCore();
bool isNewProfile();
bool isEncrypted() const; ///< Returns true if we have a password set (doesn't check the actual file on disk)
bool checkPassword(); ///< Checks whether the password is valid
bool isEncrypted() const;
bool checkPassword();
QString getPassword() const;
void setPassword(const QString &newPassword); ///< Changes the encryption password and re-saves everything with it
void setPassword(const QString &newPassword);
const TOX_PASS_KEY& getPasskey() const;
QByteArray loadToxSave(); ///< Loads the profile's .tox save from file, unencrypted
void saveToxSave(); ///< Saves the profile's .tox save, encrypted if needed. Invalid on deleted profiles.
void saveToxSave(QByteArray data); ///< Write the .tox save, encrypted if needed. Invalid on deleted profiles.
QByteArray loadToxSave();
void saveToxSave();
void saveToxSave(QByteArray data);
QPixmap loadAvatar(); ///< Get our avatar from cache
QPixmap loadAvatar(const QString& ownerId); ///< Get a contact's avatar from cache
QByteArray loadAvatarData(const QString& ownerId); ///< Get a contact's avatar from cache
QByteArray loadAvatarData(const QString& ownerId, const QString& password); ///< Get a contact's avatar from cache, with a specified profile password.
void saveAvatar(QByteArray pic, const QString& ownerId); ///< Save an avatar to cache
QByteArray getAvatarHash(const QString& ownerId); ///< Get the tox hash of a cached avatar
void removeAvatar(const QString& ownerId); ///< Removes a cached avatar
void removeAvatar(); ///< Removes our own avatar
QPixmap loadAvatar();
QPixmap loadAvatar(const QString& ownerId);
QByteArray loadAvatarData(const QString& ownerId);
QByteArray loadAvatarData(const QString& ownerId, const QString& password);
void saveAvatar(QByteArray pic, const QString& ownerId);
QByteArray getAvatarHash(const QString& ownerId);
void removeAvatar(const QString& ownerId);
void removeAvatar();
/// Returns true if the history is enabled in the settings, and loaded successfully for this profile
bool isHistoryEnabled();
/// May return a nullptr if the history failed to load
History* getHistory();
/// Removes the profile permanently
/// It is invalid to call loadToxSave or saveToxSave on a deleted profile
/// Updates the profiles vector
/// Returns a vector of filenames that could not be removed.
QVector<QString> remove();
/// Tries to rename the profile
bool rename(QString newName);
/// Scan for profile, automatically importing them if needed
/// NOT thread-safe
static void scanProfiles();
static QVector<QString> getProfiles();
static bool exists(QString name);
static bool isEncrypted(QString name); ///< Returns false on error. Checks the actual file on disk.
static bool isEncrypted(QString name);
private:
Profile(QString name, const QString &password, bool newProfile);
/// Lists all the files in the config dir with a given extension
/// Pass the raw extension, e.g. "jpeg" not ".jpeg".
static QVector<QString> getFilesByExt(QString extension);
/// Creates a .ini file for the given .tox profile
/// Only pass the basename, without extension
static void importProfile(QString name);
/// Gets the path of the avatar file cached by this profile and corresponding to this owner ID
/// If forceUnencrypted, we return the path to the plaintext file even if we're an encrypted profile
QString avatarPath(const QString& ownerId, bool forceUnencrypted = false);
private:
@ -110,11 +88,9 @@ private:
QString name, password;
TOX_PASS_KEY passkey;
std::unique_ptr<History> history;
bool newProfile; ///< True if this is a newly created profile, with no .tox save file yet.
bool isRemoved; ///< True if the profile has been removed by remove()
bool newProfile;
bool isRemoved;
static QVector<QString> profiles;
/// How much data we need to read to check if the file is encrypted
/// Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined
static constexpr int encryptHeaderSize = 8;
};

View File

@ -23,6 +23,14 @@
#include <QDir>
#include <QDebug>
/**
@class ProfileLocker
@brief Locks a Tox profile so that multiple instances can not use the same profile.
Only one lock can be acquired at the same time, which means
that there is little need for manually unlocking.
The current lock will expire if you exit or acquire a new one.
*/
using namespace std;
unique_ptr<QLockFile> ProfileLocker::lockfile;
@ -33,6 +41,14 @@ QString ProfileLocker::lockPathFromName(const QString& name)
return Settings::getInstance().getSettingsDirPath()+'/'+name+".lock";
}
/**
@brief Checks if a profile is currently locked by *another* instance.
If we own the lock, we consider it lockable.
There is no guarantee that the result will still be valid by the
time it is returned, this is provided on a best effort basis.
@param profile Profile name to check.
@return True, if profile locked, false otherwise.
*/
bool ProfileLocker::isLockable(QString profile)
{
// If we already have the lock, it's definitely lockable
@ -43,6 +59,11 @@ bool ProfileLocker::isLockable(QString profile)
return newLock.tryLock();
}
/**
@brief Tries to acquire the lock on a profile, will not block.
@param profile Profile to lock.
@return Returns true if we already own the lock.
*/
bool ProfileLocker::lock(QString profile)
{
if (lockfile && curLockName == profile)
@ -62,16 +83,26 @@ bool ProfileLocker::lock(QString profile)
return true;
}
/**
@brief Releases the lock on the current profile.
*/
void ProfileLocker::unlock()
{
if (!lockfile)
return;
lockfile->unlock();
delete lockfile.release();
lockfile = nullptr;
curLockName.clear();
}
/**
@brief Check that we actually own the lock.
In case the file was deleted on disk, restore it.
If we can't get a lock, exit qTox immediately.
If we never had a lock in the first place, exit immediately.
*/
void ProfileLocker::assertLock()
{
if (!lockfile)
@ -96,17 +127,28 @@ void ProfileLocker::assertLock()
}
}
/**
@brief Print an error then exit immediately.
*/
void ProfileLocker::deathByBrokenLock()
{
qCritical() << "Lock is *BROKEN*, exiting immediately";
abort();
}
/**
@brief Chacks, that profile locked.
@return Returns true if we're currently holding a lock.
*/
bool ProfileLocker::hasLock()
{
return lockfile.operator bool();
}
/**
@brief Get current locked profile name.
@return Return the name of the currently loaded profile, a null string if there is none.
*/
QString ProfileLocker::getCurLockName()
{
if (lockfile)

View File

@ -24,39 +24,22 @@
#include <QLockFile>
#include <memory>
/// Locks a Tox profile so that multiple instances can not use the same profile.
/// Only one lock can be acquired at the same time, which means
/// that there is little need for manually unlocking.
/// The current lock will expire if you exit or acquire a new one.
class ProfileLocker
{
private:
ProfileLocker()=delete;
public:
/// Checks if a profile is currently locked by *another* instance
/// If we own the lock, we consider it lockable
/// There is no guarantee that the result will still be valid by the
/// time it is returned, this is provided on a best effort basis
static bool isLockable(QString profile);
/// Tries to acquire the lock on a profile, will not block
/// Returns true if we already own the lock
static bool lock(QString profile);
/// Releases the lock on the current profile
static void unlock();
/// Returns true if we're currently holding a lock
static bool hasLock();
/// Return the name of the currently loaded profile, a null string if there is none
static QString getCurLockName();
/// Check that we actually own the lock
/// In case the file was deleted on disk, restore it
/// If we can't get a lock, exit qTox immediately
/// If we never had a lock in the firt place, exit immediately
static void assertLock();
private:
static QString lockPathFromName(const QString& name);
static void deathByBrokenLock(); ///< Print an error then exit immediately
static void deathByBrokenLock();
private:
static std::unique_ptr<QLockFile> lockfile;

View File

@ -17,9 +17,14 @@
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "src/persistence/serialize.h"
/**
@file serialize.cpp
Most of functions in this file are unsafe unless otherwise specified.
@warning Do not use them on untrusted data (e.g. check a signature first).
*/
QByteArray doubleToData(double num)
{
union

View File

@ -25,9 +25,6 @@
#include <QByteArray>
#include <QString>
/// Most of those functions are unsafe unless otherwise specified
/// Do not use them on untrusted data (e.g. check a signature first)
QByteArray doubleToData(double num);
QByteArray floatToData(float num);
float dataToFloat(QByteArray data);

View File

@ -49,6 +49,17 @@
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
/**
@var QHash<QString, QByteArray> Settings::widgetSettings
@brief Assume all widgets have unique names
@warning Don't use it to save every single thing you want to save, use it
for some general purpose widgets, such as MainWindows or Splitters,
which have widget->saveX() and widget->loadX() methods.
@var QString Settings::toxmeInfo
@brief Toxme info like name@server
*/
const QString Settings::globalSettingsFile = "qtox.ini";
Settings* Settings::settings{nullptr};
QMutex Settings::bigLock{QMutex::Recursive};
@ -73,6 +84,9 @@ Settings::~Settings()
delete settingsThread;
}
/**
@brief Returns the singleton instance.
*/
Settings& Settings::getInstance()
{
if (!settings)
@ -379,6 +393,9 @@ void Settings::loadPersonal(Profile* profile)
ps.endGroup();
}
/**
@brief Asynchronous, saves the global settings.
*/
void Settings::saveGlobal()
{
if (QThread::currentThread() != settingsThread)
@ -498,11 +515,18 @@ void Settings::saveGlobal()
s.endGroup();
}
/**
@brief Asynchronous, saves the current profile.
*/
void Settings::savePersonal()
{
savePersonal(Nexus::getProfile());
}
/**
@brief Asynchronous, saves the profile.
@param profile Profile to save.
*/
void Settings::savePersonal(Profile* profile)
{
if (!profile)
@ -600,6 +624,10 @@ uint32_t Settings::makeProfileId(const QString& profile)
return dwords[0] ^ dwords[1] ^ dwords[2] ^ dwords[3];
}
/**
@brief Get path to directory, where the settings files are stored.
@return Path to settings directory, ends with a directory separator.
*/
QString Settings::getSettingsDirPath()
{
QMutexLocker locker{&bigLock};
@ -619,6 +647,10 @@ QString Settings::getSettingsDirPath()
#endif
}
/**
@brief Get path to directory, where the application data are stored.
@return Path to application data, ends with a directory separator.
*/
QString Settings::getAppDataDirPath()
{
QMutexLocker locker{&bigLock};
@ -640,6 +672,10 @@ QString Settings::getAppDataDirPath()
#endif
}
/**
@brief Get path to directory, where the application cache are stored.
@return Path to application cache, ends with a directory separator.
*/
QString Settings::getAppCacheDirPath()
{
QMutexLocker locker{&bigLock};
@ -1846,6 +1882,11 @@ void Settings::setAutoLogin(bool state)
autoLogin = state;
}
/**
@brief Write a default personal .ini settings file for a profile.
@param basename Filename without extension to save settings.
@example If basename is "profile", settings will be saved in profile.ini
*/
void Settings::createPersonal(QString basename)
{
QString path = getSettingsDirPath() + QDir::separator() + basename + ".ini";
@ -1862,6 +1903,9 @@ void Settings::createPersonal(QString basename)
ps.endGroup();
}
/**
@brief Creates a path to the settings dir, if it doesn't already exist
*/
void Settings::createSettingsDir()
{
QString dir = Settings::getSettingsDirPath();
@ -1870,6 +1914,9 @@ void Settings::createSettingsDir()
qCritical() << "Error while creating directory " << dir;
}
/**
@brief Waits for all asynchronous operations to complete
*/
void Settings::sync()
{
if (QThread::currentThread() != settingsThread)

View File

@ -44,15 +44,15 @@ class Settings : public QObject
public:
static Settings& getInstance();
static void destroyInstance();
QString getSettingsDirPath(); ///< The returned path ends with a directory separator
QString getAppDataDirPath(); ///< The returned path ends with a directory separator
QString getAppCacheDirPath(); ///< The returned path ends with a directory separator
QString getSettingsDirPath();
QString getAppDataDirPath();
QString getAppCacheDirPath();
void createSettingsDir(); ///< Creates a path to the settings dir, if it doesn't already exist
void createPersonal(QString basename); ///< Write a default personal .ini settings file for a profile
void createSettingsDir();
void createPersonal(QString basename);
void savePersonal(); ///< Asynchronous, saves the current profile
void savePersonal(Profile *profile); ///< Asynchronous
void savePersonal();
void savePersonal(Profile *profile);
void loadGlobal();
void loadPersonal();
@ -67,8 +67,8 @@ public:
public slots:
void saveGlobal(); ///< Asynchronous
void sync(); ///< Waits for all asynchronous operations to complete
void saveGlobal();
void sync();
signals:
void dhtServerListChanged();
@ -76,7 +76,6 @@ signals:
void emojiFontChanged();
public:
// Getter/setters
const QList<DhtServer>& getDhtServerList() const;
void setDhtServerList(const QList<DhtServer>& newDhtServerList);
@ -329,10 +328,6 @@ public:
void removeFriendRequest(int index);
void readFriendRequest(int index);
// Assume all widgets have unique names
// Don't use it to save every single thing you want to save, use it
// for some general purpose widgets, such as MainWindows or Splitters,
// which have widget->saveX() and widget->loadX() methods.
QByteArray getWidgetData(const QString& uniqueName) const;
void setWidgetData(const QString& uniqueName, const QByteArray& data);
@ -401,7 +396,7 @@ private:
uint32_t currentProfileId;
// Toxme Info
QString toxmeInfo; // name@server
QString toxmeInfo;
QString toxmeBio;
bool toxmePriv;
QString toxmePass;

View File

@ -28,8 +28,37 @@
#include <memory>
#include <cassert>
/**
@class SettingsSerializer
@brief Serializes a QSettings's data in an (optionally) encrypted binary format.
SettingsSerializer can detect regular .ini files and serialized ones,
it will read both regular and serialized .ini, but only save in serialized format.
The file is encrypted with the current profile's password, if any.
The file is only written to disk if save() is called, the destructor does not save to disk
All member functions are reentrant, but not thread safe.
@enum SettingsSerializer::RecordTag
@var Value
Followed by a QString key then a QVariant value
@var GroupStart
Followed by a QString group name
@var ArrayStart
Followed by a QString array name and a vuint array size
@var ArrayValue
Followed by a vuint array index, a QString key then a QVariant value
@var ArrayEnd
Not followed by any data
*/
enum class RecordTag : uint8_t
{
};
using namespace std;
/**
@var static const char magic[];
@brief Little endian ASCII "QTOX" magic
*/
const char SettingsSerializer::magic[] = {0x51,0x54,0x4F,0x58};
QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag)
@ -199,6 +228,11 @@ SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key)
return const_cast<Value*>(const_cast<const SettingsSerializer*>(this)->findValue(key));
}
/**
@brief Checks if the file is serialized settings.
@param filePath Path to file to check.
@return False on error, true otherwise.
*/
bool SettingsSerializer::isSerializedFormat(QString filePath)
{
QFile f(filePath);
@ -210,6 +244,9 @@ bool SettingsSerializer::isSerializedFormat(QString filePath)
return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted((uint8_t*)fmagic);
}
/**
@brief Loads the settings from file.
*/
void SettingsSerializer::load()
{
if (isSerializedFormat(path))
@ -218,6 +255,9 @@ void SettingsSerializer::load()
readIni();
}
/**
@brief Saves the current settings back to file
*/
void SettingsSerializer::save()
{
QSaveFile f(path);
@ -545,6 +585,11 @@ void SettingsSerializer::readIni()
group = array = -1;
}
/**
@brief Remove group.
@note The group must be empty.
@param group ID of group to remove.
*/
void SettingsSerializer::removeGroup(int group)
{
assert(group<groups.size());

View File

@ -25,21 +25,15 @@
#include <QString>
#include <QDataStream>
/// Serializes a QSettings's data in an (optionally) encrypted binary format
/// SettingsSerializer can detect regular .ini files and serialized ones,
/// it will read both regular and serialized .ini, but only save in serialized format.
/// The file is encrypted with the current profile's password, if any.
/// The file is only written to disk if save() is called, the destructor does not save to disk
/// All member functions are reentrant, but not thread safe.
class SettingsSerializer
{
public:
SettingsSerializer(QString filePath, const QString &password=QString());
static bool isSerializedFormat(QString filePath); ///< Check if the file is serialized settings. False on error.
static bool isSerializedFormat(QString filePath);
void load(); ///< Loads the settings from file
void save(); ///< Saves the current settings back to file
void load();
void save();
void beginGroup(const QString &prefix);
void endGroup();
@ -55,15 +49,10 @@ public:
private:
enum class RecordTag : uint8_t
{
/// Followed by a QString key then a QVariant value
Value=0,
/// Followed by a QString group name
GroupStart=1,
/// Followed by a QString array name and a vuint array size
ArrayStart=2,
/// Followed by a vuint array index, a QString key then a QVariant value
ArrayValue=3,
/// Not followed by any data
ArrayEnd=4,
};
friend QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag);
@ -94,7 +83,7 @@ private:
void readSerialized();
void readIni();
void removeValue(const QString& key);
void removeGroup(int group); ///< The group must be empty
void removeGroup(int group);
void writePackedVariant(QDataStream& dataStream, const QVariant& v);
private:
@ -104,7 +93,7 @@ private:
QVector<QString> groups;
QVector<Array> arrays;
QVector<Value> values;
static const char magic[]; ///< Little endian ASCII "QTOX" magic
static const char magic[];
};
#endif // SETTINGSSERIALIZER_H

View File

@ -35,6 +35,23 @@
#include <QStringBuilder>
#include <QtConcurrent/QtConcurrentRun>
/**
@class SmileyPack
@brief Maps emoticons to smileys.
@var QHash<QString, QString> SmileyPack::filenameTable
@brief Matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png"
@var QHash<QString, QIcon> SmileyPack::iconCache
@brief representation of a smiley ie. "happy.png" -> data
@var QList<QStringList> SmileyPack::emoticons
@brief {{ ":)", ":-)" }, {":(", ...}, ... }
@var QString SmileyPack::path
@brief directory containing the cfg and image files
*/
SmileyPack::SmileyPack()
{
loadingMutex.lock();
@ -42,6 +59,9 @@ SmileyPack::SmileyPack()
connect(&Settings::getInstance(), &Settings::smileyPackChanged, this, &SmileyPack::onSmileyPackChanged);
}
/**
@brief Returns the singleton instance.
*/
SmileyPack& SmileyPack::getInstance()
{
static SmileyPack smileyPack;
@ -92,6 +112,12 @@ bool SmileyPack::isValid(const QString &filename)
return QFile(filename).exists();
}
/**
@brief Load smile pack
@note The caller must lock loadingMutex and should run it in a thread
@param filename Filename of smilepack.
@return False if cannot open file, true otherwise.
*/
bool SmileyPack::load(const QString& filename)
{
// discard old data

View File

@ -32,7 +32,6 @@
":/smileys", "./smileys", "/usr/share/qtox/smileys", "/usr/share/emoticons", "~/.kde4/share/emoticons", "~/.kde/share/emoticons" \
}
//maps emoticons to smileys
class SmileyPack : public QObject
{
Q_OBJECT
@ -54,14 +53,14 @@ private:
SmileyPack(SmileyPack&) = delete;
SmileyPack& operator=(const SmileyPack&) = delete;
bool load(const QString& filename); ///< The caller must lock loadingMutex and should run it in a thread
bool load(const QString& filename);
void cacheSmiley(const QString& name);
QIcon getCachedSmiley(const QString& key);
QHash<QString, QString> filenameTable; // matches an emoticon to its corresponding smiley ie. ":)" -> "happy.png"
QHash<QString, QIcon> iconCache; // representation of a smiley ie. "happy.png" -> data
QList<QStringList> emoticons; // {{ ":)", ":-)" }, {":(", ...}, ... }
QString path; // directory containing the cfg and image files
QHash<QString, QString> filenameTable;
QHash<QString, QIcon> iconCache;
QList<QStringList> emoticons;
QString path;
mutable QMutex loadingMutex;
};

View File

@ -33,6 +33,12 @@ bool toxSaveEventHandler(const QByteArray& eventData)
return true;
}
/**
@brief Import new profile.
@note Will wait until the core is ready first.
@param path Path to .tox file.
@return True if import success, false, otherwise.
*/
bool handleToxSave(const QString& path)
{
Core* core = Core::getInstance();
@ -59,7 +65,7 @@ bool handleToxSave(const QString& path)
return false;
}
QString profilePath = Settings::getInstance().getSettingsDirPath()+profile+Core::TOX_EXT;
QString profilePath = Settings::getInstance().getSettingsDirPath() + profile + Core::TOX_EXT;
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(QObject::tr("Profile already exists", "import confirm title"),
QObject::tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))

View File

@ -23,7 +23,6 @@
class QString;
class QByteArray;
/// Will wait until the core is ready first
bool handleToxSave(const QString& path);
// Internals

View File

@ -38,6 +38,28 @@ extern "C" {
#include "src/platform/camera/avfoundation.h"
#endif
/**
@class CameraDevice
Maintains an FFmpeg context for open camera devices,
takes care of sharing the context accross users and closing
the camera device when not in use. The device can be opened
recursively, and must then be closed recursively
*/
/**
@var const QString CameraDevice::devName
@brief Short name of the device
@var AVFormatContext* CameraDevice::context
@brief Context of the open device, must always be valid
@var std::atomic_int CameraDevice::refcount;
@brief Number of times the device was opened
*/
QHash<QString, CameraDevice*> CameraDevice::openDevices;
QMutex CameraDevice::openDeviceLock, CameraDevice::iformatLock;
AVInputFormat* CameraDevice::iformat{nullptr};
@ -103,6 +125,18 @@ out:
return dev;
}
/**
@brief Opens a device.
Opens a device, creating a new one if needed
If the device is alreay open in another mode, the mode
will be ignored and the existing device is used
If the mode does not exist, a new device can't be opened.
@param devName Device name to open.
@param mode Mode of device to open.
@return CameraDevice if the device could be opened, nullptr otherwise.
*/
CameraDevice* CameraDevice::open(QString devName, VideoMode mode)
{
if (!getDefaultInputFormat())
@ -205,11 +239,20 @@ CameraDevice* CameraDevice::open(QString devName, VideoMode mode)
return dev;
}
/**
@brief Opens the device again. Never fails
*/
void CameraDevice::open()
{
++refcount;
}
/**
@brief Closes the device. Never fails.
@note If returns true, "this" becomes invalid.
@return True, if device finally deleted (closed last reference),
false otherwise (if other references exist).
*/
bool CameraDevice::close()
{
if (--refcount > 0)
@ -223,6 +266,11 @@ bool CameraDevice::close()
return true;
}
/**
@brief Get raw device list
@note Uses avdevice_list_devices
@return Raw device list
*/
QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
{
QVector<QPair<QString, QString>> devices;
@ -234,11 +282,13 @@ QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
AVFormatContext *s;
if (!(s = avformat_alloc_context()))
return devices;
if (!iformat->priv_class || !AV_IS_INPUT_DEVICE(iformat->priv_class->category))
{
avformat_free_context(s);
return devices;
}
s->iformat = iformat;
if (s->iformat->priv_data_size > 0)
{
@ -280,7 +330,7 @@ QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
// Convert the list to a QVector
devices.resize(devlist->nb_devices);
for (int i=0; i<devlist->nb_devices; i++)
for (int i = 0; i < devlist->nb_devices; i++)
{
AVDeviceInfo* dev = devlist->devices[i];
devices[i].first = dev->device_name;
@ -290,6 +340,11 @@ QVector<QPair<QString, QString>> CameraDevice::getRawDeviceListGeneric()
return devices;
}
/**
@brief Get device list with desciption
@return A list of device names and descriptions.
The names are the first part of the pair and can be passed to open(QString).
*/
QVector<QPair<QString, QString>> CameraDevice::getDeviceList()
{
QVector<QPair<QString, QString>> devices;
@ -336,6 +391,11 @@ QVector<QPair<QString, QString>> CameraDevice::getDeviceList()
return devices;
}
/**
@brief Get the default device name.
@return The short name of the default device
This is either the device in the settings or the system default.
*/
QString CameraDevice::getDefaultDeviceName()
{
QString defaultdev = Settings::getInstance().getVideoDev();
@ -354,11 +414,20 @@ QString CameraDevice::getDefaultDeviceName()
return devlist[0].first;
}
/**
@brief Checks if a device name specifies a display.
@param devName Device name to check.
@return True, if device is screen, false otherwise.
*/
bool CameraDevice::isScreen(const QString &devName)
{
return devName.startsWith("x11grab") || devName.startsWith("gdigrab");
}
/**
@brief Get list of resolutions and position of screens
@return Vector of avaliable screen modes with offset
*/
QVector<VideoMode> CameraDevice::getScreenModes()
{
QList<QScreen*> screens = QApplication::screens();
@ -376,6 +445,11 @@ QVector<VideoMode> CameraDevice::getScreenModes()
return result;
}
/**
@brief Get the list of video modes for a device.
@param devName Device name to get nodes from.
@return Vector of available modes for the device.
*/
QVector<VideoMode> CameraDevice::getVideoModes(QString devName)
{
Q_UNUSED(devName);
@ -401,6 +475,11 @@ QVector<VideoMode> CameraDevice::getVideoModes(QString devName)
return {};
}
/**
@brief Get the name of the pixel format of a video mode.
@param pixel_format Pixel format to get the name from.
@return Name of the pixel format.
*/
QString CameraDevice::getPixelFormatString(uint32_t pixel_format)
{
#ifdef Q_OS_LINUX
@ -410,6 +489,13 @@ QString CameraDevice::getPixelFormatString(uint32_t pixel_format)
#endif
}
/**
@brief Compare two pixel formats.
@param a First pixel format to compare.
@param b Second pixel format to compare.
@return True if we prefer format a to b,
false otherwise (such as if there's no preference).
*/
bool CameraDevice::betterPixelFormat(uint32_t a, uint32_t b)
{
#ifdef Q_OS_LINUX
@ -419,6 +505,10 @@ bool CameraDevice::betterPixelFormat(uint32_t a, uint32_t b)
#endif
}
/**
@brief Sets CameraDevice::iformat to default.
@return True if success, false if failure.
*/
bool CameraDevice::getDefaultInputFormat()
{
QMutexLocker locker(&iformatLock);

View File

@ -33,55 +33,36 @@ struct AVInputFormat;
struct AVDeviceInfoList;
struct AVDictionary;
/// Maintains an FFmpeg context for open camera devices,
/// takes care of sharing the context accross users
/// and closing the camera device when not in use.
/// The device can be opened recursively,
/// and must then be closed recursively
class CameraDevice
{
public:
/// Opens a device, creating a new one if needed
/// If the device is alreay open in another mode, the mode
/// will be ignored and the existing device is used
/// If the mode does not exist, a new device can't be opened
/// Returns a nullptr if the device couldn't be opened
static CameraDevice* open(QString devName, VideoMode mode = VideoMode());
void open(); ///< Opens the device again. Never fails
bool close(); ///< Closes the device. Never fails. If returns true, "this" becomes invalid
void open();
bool close();
/// Returns a list of device names and descriptions
/// The names are the first part of the pair and can be passed to open(QString)
static QVector<QPair<QString, QString>> getDeviceList();
/// Get the list of video modes for a device
static QVector<VideoMode> getVideoModes(QString devName);
/// Get the name of the pixel format of a video mode
static QString getPixelFormatString(uint32_t pixel_format);
/// Returns true if we prefer format a to b, false otherwise (such as if there's no preference)
static bool betterPixelFormat(uint32_t a, uint32_t b);
/// Returns the short name of the default defice
/// This is either the device in the settings
/// or the system default.
static QString getDefaultDeviceName();
/// Checks if a device name specifies a display
static bool isScreen(const QString &devName);
private:
CameraDevice(const QString &devName, AVFormatContext *context);
static CameraDevice* open(QString devName, AVDictionary** options);
static bool getDefaultInputFormat(); ///< Sets CameraDevice::iformat, returns success/failure
static QVector<QPair<QString, QString> > getRawDeviceListGeneric(); ///< Uses avdevice_list_devices
static QVector<VideoMode> getScreenModes(); ///< Returns avaliable screen modes with offset
static bool getDefaultInputFormat();
static QVector<QPair<QString, QString> > getRawDeviceListGeneric();
static QVector<VideoMode> getScreenModes();
public:
const QString devName; ///< Short name of the device
AVFormatContext* context; ///< Context of the open device, must always be valid
const QString devName;
AVFormatContext* context;
private:
std::atomic_int refcount; ///< Number of times the device was opened
std::atomic_int refcount;
static QHash<QString, CameraDevice*> openDevices;
static QMutex openDeviceLock, iformatLock;
static AVInputFormat* iformat, *idesktopFormat;

View File

@ -33,6 +33,57 @@ extern "C" {
#include "cameradevice.h"
#include "videoframe.h"
/**
@class CameraSource
@brief This class is a wrapper to share a camera's captured video frames
It allows objects to suscribe and unsuscribe to the stream, starting
the camera and streaming new video frames only when needed.
This is a singleton, since we can only capture from one
camera at the same time without thread-safety issues.
The source is lazy in the sense that it will only keep the video
device open as long as there are subscribers, the source can be
open but the device closed if there are zero subscribers.
*/
/**
@var QVector<std::weak_ptr<VideoFrame>> CameraSource::freelist
@brief Frames that need freeing before we can safely close the device
@var QFuture<void> CameraSource::streamFuture
@brief Future of the streaming thread
@var QString CameraSource::deviceName
@brief Short name of the device for CameraDevice's open(QString)
@var CameraDevice* CameraSource::device
@brief Non-owning pointer to an open CameraDevice, or nullptr. Not atomic, synced with memfences when becomes null.
@var VideoMode CameraSource::mode
@brief What mode we tried to open the device in, all zeros means default mode
@var AVCodecContext* CameraSource::cctx
@brief Codec context of the camera's selected video stream
@var AVCodecContext* CameraSource::cctxOrig
@brief Codec context of the camera's selected video stream
@var int CameraSource::videoStreamIndex
@brief A camera can have multiple streams, this is the one we're decoding
@var QMutex CameraSource::biglock
@brief True when locked. Faster than mutexes for video decoding.
@var QMutex CameraSource::freelistLock
@brief True when locked. Faster than mutexes for video decoding.
@var std::atomic_bool CameraSource::streamBlocker
@brief Holds the streaming thread still when true
@var std::atomic_int CameraSource::subscriptions
@brief Remember how many times we subscribed for RAII
*/
CameraSource* CameraSource::instance{nullptr};
CameraSource::CameraSource()
@ -45,6 +96,9 @@ CameraSource::CameraSource()
avdevice_register_all();
}
/**
@brief Returns the singleton instance.
*/
CameraSource& CameraSource::getInstance()
{
if (!instance)
@ -61,6 +115,12 @@ void CameraSource::destroyInstance()
}
}
/**
@brief Opens the source for the camera device.
@note If a device is already open, the source will seamlessly switch to the new device.
Opens the source for the camera device in argument, in the settings, or the system default.
*/
void CameraSource::open()
{
open(CameraDevice::getDefaultDeviceName());
@ -103,6 +163,11 @@ void CameraSource::open(const QString& DeviceName, VideoMode Mode)
streamBlocker = false;
}
/**
@brief Stops streaming.
Equivalent to opening the source with the video device "none".
*/
void CameraSource::close()
{
open("none");
@ -214,6 +279,11 @@ void CameraSource::unsubscribe()
subscriptions--;
}
/**
@brief Opens the video device and starts streaming.
@note Callers must own the biglock.
@return True if success, false otherwise.
*/
bool CameraSource::openDevice()
{
qDebug() << "Opening device " << deviceName;
@ -296,6 +366,10 @@ bool CameraSource::openDevice()
return true;
}
/**
@brief Closes the video device and stops streaming.
@note Callers must own the biglock.
*/
void CameraSource::closeDevice()
{
qDebug() << "Closing device "<<deviceName;
@ -324,6 +398,10 @@ void CameraSource::closeDevice()
std::atomic_thread_fence(std::memory_order_release);
}
/**
@brief Blocking. Decodes video stream and emits new frames.
@note Designed to run in its own thread.
*/
void CameraSource::stream()
{
auto streamLoop = [=]()
@ -384,12 +462,28 @@ void CameraSource::stream()
}
}
/**
@brief CameraSource::freelistCallback
@param freelistIndex
All VideoFrames must be deleted or released before we can close the device
or the device will forcibly free them, and then ~VideoFrame() will double free.
In theory very careful coding from our users could ensure all VideoFrames
die before unsubscribing, even the ones currently in flight in the metatype system.
But that's just asking for trouble and mysterious crashes, so we'll just
maintain a freelist and have all VideoFrames tell us when they die so we can forget them.
*/
void CameraSource::freelistCallback(int freelistIndex)
{
QMutexLocker l{&freelistLock};
freelist[freelistIndex].reset();
}
/**
@brief Get the index of a free slot in the freelist.
@note Callers must hold the freelistLock.
@return Index of a free slot.
*/
int CameraSource::getFreelistSlotLockless()
{
int size = freelist.size();

View File

@ -31,17 +31,6 @@
class CameraDevice;
struct AVCodecContext;
/**
* This class is a wrapper to share a camera's captured video frames
* It allows objects to suscribe and unsuscribe to the stream, starting
* the camera and streaming new video frames only when needed.
* This is a singleton, since we can only capture from one
* camera at the same time without thread-safety issues.
* The source is lazy in the sense that it will only keep the video
* device open as long as there are subscribers, the source can be
* open but the device closed if there are zero subscribers.
**/
class CameraSource : public VideoSource
{
Q_OBJECT
@ -49,12 +38,10 @@ class CameraSource : public VideoSource
public:
static CameraSource& getInstance();
static void destroyInstance();
/// Opens the source for the camera device in argument, in the settings, or the system default
/// If a device is already open, the source will seamlessly switch to the new device
void open();
void open(const QString& deviceName);
void open(const QString& deviceName, VideoMode mode);
void close(); ///< Equivalent to opening the source with the video device "none". Stops streaming.
void close();
bool isOpen();
// VideoSource interface
@ -67,34 +54,24 @@ signals:
private:
CameraSource();
~CameraSource();
/// Blocking. Decodes video stream and emits new frames.
/// Designed to run in its own thread.
void stream();
/// All VideoFrames must be deleted or released before we can close the device
/// or the device will forcibly free them, and then ~VideoFrame() will double free.
/// In theory very careful coding from our users could ensure all VideoFrames
/// die before unsubscribing, even the ones currently in flight in the metatype system.
/// But that's just asking for trouble and mysterious crashes, so we'll just
/// maintain a freelist and have all VideoFrames tell us when they die so we can forget them.
void freelistCallback(int freelistIndex);
/// Get the index of a free slot in the freelist
/// Callers must hold the freelistLock
int getFreelistSlotLockless();
bool openDevice(); ///< Callers must own the biglock. Actually opens the video device and starts streaming.
void closeDevice(); ///< Callers must own the biglock. Actually closes the video device and stops streaming.
bool openDevice();
void closeDevice();
private:
QVector<std::weak_ptr<VideoFrame>> freelist; ///< Frames that need freeing before we can safely close the device
QFuture<void> streamFuture; ///< Future of the streaming thread
QString deviceName; ///< Short name of the device for CameraDevice's open(QString)
CameraDevice* device; ///< Non-owning pointer to an open CameraDevice, or nullptr. Not atomic, synced with memfences when becomes null.
VideoMode mode; ///< What mode we tried to open the device in, all zeros means default mode
AVCodecContext* cctx, *cctxOrig; ///< Codec context of the camera's selected video stream
int videoStreamIndex; ///< A camera can have multiple streams, this is the one we're decoding
QMutex biglock, freelistLock; ///< True when locked. Faster than mutexes for video decoding.
QVector<std::weak_ptr<VideoFrame>> freelist;
QFuture<void> streamFuture;
QString deviceName;
CameraDevice* device;
VideoMode mode;
AVCodecContext* cctx, *cctxOrig;
int videoStreamIndex;
QMutex biglock, freelistLock;
std::atomic_bool _isOpen;
std::atomic_bool streamBlocker; ///< Holds the streaming thread still when true
std::atomic_int subscriptions; ///< Remember how many times we subscribed for RAII
std::atomic_bool streamBlocker;
std::atomic_int subscriptions;
static CameraSource* instance;
};

View File

@ -25,12 +25,34 @@ extern "C" {
#include "corevideosource.h"
#include "videoframe.h"
/**
@class CoreVideoSource
@brief A VideoSource that emits frames received by Core.
*/
/**
@var std::atomic_int subscribers
@brief Number of suscribers
@var std::atomic_bool deleteOnClose
@brief If true, self-delete after the last suscriber is gone
*/
/**
@brief CoreVideoSource constructor.
@note Only CoreAV should create a CoreVideoSource since
only CoreAV can push images to it.
*/
CoreVideoSource::CoreVideoSource()
: subscribers{0}, deleteOnClose{false},
stopped{false}
{
}
/**
@brief Makes a copy of the vpx_image_t and emits it as a new VideoFrame.
@param vpxframe Frame to copy.
*/
void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe)
{
if (stopped)
@ -108,12 +130,22 @@ void CoreVideoSource::unsubscribe()
biglock.unlock();
}
/**
@brief Setup delete on close
@param If true, self-delete after the last suscriber is gone
*/
void CoreVideoSource::setDeleteOnClose(bool newstate)
{
QMutexLocker locker(&biglock);
deleteOnClose = newstate;
}
/**
@brief Stopping the source.
@see The callers in CoreAV for the rationale
Stopping the source will block any pushFrame calls from doing anything
*/
void CoreVideoSource::stopSource()
{
QMutexLocker locker(&biglock);

View File

@ -26,7 +26,6 @@
#include "videosource.h"
#include <QMutex>
/// A VideoSource that emits frames received by Core
class CoreVideoSource : public VideoSource
{
Q_OBJECT
@ -36,23 +35,17 @@ public:
virtual void unsubscribe() override;
private:
// Only CoreAV should create a CoreVideoSource since
// only CoreAV can push images to it
CoreVideoSource();
/// Makes a copy of the vpx_image_t and emits it as a new VideoFrame
void pushFrame(const vpx_image_t *frame);
/// If true, self-delete after the last suscriber is gone
void setDeleteOnClose(bool newstate);
/// Stopping the source will block any pushFrame calls from doing anything
/// See the callers in CoreAV for the rationale
void stopSource();
void restartSource();
private:
std::atomic_int subscribers; ///< Number of suscribers
std::atomic_bool deleteOnClose; ///< If true, self-delete after the last suscriber is gone
std::atomic_int subscribers;
std::atomic_bool deleteOnClose;
QMutex biglock;
std::atomic_bool stopped;

View File

@ -35,6 +35,7 @@ NetCamView::NetCamView(int friendId, QWidget* parent)
: GenericNetCamView(parent)
, selfFrame{nullptr}
, friendId{friendId}
, e(false)
{
QString id = FriendList::findFriend(friendId)->getToxId().toString();
videoSurface = new VideoSurface(Nexus::getProfile()->loadAvatar(id), this);

View File

@ -55,7 +55,7 @@ private:
VideoSurface* selfVideoSurface;
MovableWidget* selfFrame;
int friendId;
bool e = false;
bool e;
QVector<QMetaObject::Connection> connections;
};

View File

@ -30,6 +30,17 @@ extern "C" {
#include "videoframe.h"
#include "camerasource.h"
/**
@class VideoFrame
VideoFrame takes ownership of an AVFrame* and allows fast conversions to other formats
Ownership of all video frame buffers is kept by the VideoFrame, even after conversion
All references to the frame data become invalid when the VideoFrame is deleted
We try to avoid pixel format conversions as much as possible, at the cost of some memory
All methods are thread-safe. If provided freelistCallback will be called by the destructor,
unless releaseFrame was called in between.
*/
VideoFrame::VideoFrame(AVFrame* frame, int w, int h, int fmt, std::function<void()> freelistCallback)
: freelistCallback{freelistCallback},
frameOther{nullptr}, frameYUV420{nullptr}, frameRGB24{nullptr},
@ -70,6 +81,10 @@ VideoFrame::VideoFrame(AVFrame* frame)
{
}
/**
@brief VideoFrame constructor. Disable copy.
@note Use a shared_ptr if you need copies.
*/
VideoFrame::~VideoFrame()
{
if (freelistCallback)
@ -78,6 +93,11 @@ VideoFrame::~VideoFrame()
releaseFrameLockless();
}
/**
@brief Converts the VideoFrame to a QImage that shares our internal video buffer.
@param size Size of resulting image.
@return Converted image to RGB24 color model.
*/
QImage VideoFrame::toQImage(QSize size)
{
if (!convertToRGB24(size))
@ -88,6 +108,11 @@ QImage VideoFrame::toQImage(QSize size)
return QImage(*frameRGB24->data, frameRGB24->width, frameRGB24->height, *frameRGB24->linesize, QImage::Format_RGB888);
}
/**
@brief Converts the VideoFrame to a vpx_image_t.
Converts the VideoFrame to a vpx_image_t that shares our internal video buffer.
@return Converted image to vpx_image format.
*/
vpx_image *VideoFrame::toVpxImage()
{
vpx_image* img = vpx_img_alloc(nullptr, VPX_IMG_FMT_I420, width, height, 0);
@ -240,6 +265,12 @@ bool VideoFrame::convertToYUV420()
return true;
}
/**
@brief Frees all frame memory.
Frees all internal buffers and frame data, removes the freelistCallback
This makes all converted objects that shares our internal buffers invalid.
*/
void VideoFrame::releaseFrame()
{
QMutexLocker locker(&biglock);
@ -269,6 +300,10 @@ void VideoFrame::releaseFrameLockless()
}
}
/**
@brief Return the size of the original frame
@return The size of the original frame
*/
QSize VideoFrame::getSize()
{
return {width, height};

View File

@ -28,12 +28,6 @@ struct AVFrame;
struct AVCodecContext;
struct vpx_image;
/// VideoFrame takes ownership of an AVFrame* and allows fast conversions to other formats
/// Ownership of all video frame buffers is kept by the VideoFrame, even after conversion
/// All references to the frame data become invalid when the VideoFrame is deleted
/// We try to avoid pixel format conversions as much as possible, at the cost of some memory
/// All methods are thread-safe. If provided freelistCallback will be called by the destructor,
/// unless releaseFrame was called in between.
class VideoFrame
{
public:
@ -42,17 +36,11 @@ public:
VideoFrame(AVFrame* frame, int w, int h, int fmt, std::function<void()> freelistCallback);
~VideoFrame();
/// Return the size of the original frame
QSize getSize();
/// Frees all internal buffers and frame data, removes the freelistCallback
/// This makes all converted objects that shares our internal buffers invalid
void releaseFrame();
/// Converts the VideoFrame to a QImage that shares our internal video buffer
QImage toQImage(QSize size = QSize());
/// Converts the VideoFrame to a vpx_image_t that shares our internal video buffer
/// Free it with operator delete, NOT vpx_img_free
vpx_image* toVpxImage();
protected:
@ -61,7 +49,6 @@ protected:
void releaseFrameLockless();
private:
// Disable copy. Use a shared_ptr if you need copies.
VideoFrame(const VideoFrame& other)=delete;
VideoFrame& operator=(const VideoFrame& other)=delete;

75
src/video/videomode.cpp Normal file
View File

@ -0,0 +1,75 @@
/*
Copyright © 2015-2016 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "videomode.h"
/**
@struct VideoMode
@brief Describes a video mode supported by a device.
@var unsigned short VideoMode::width, VideoMode::height
@brief Displayed video resolution (NOT frame resolution).
@var unsigned short VideoMode::x, VideoMode::y
@brief Coordinates of upper-left corner.
@var float VideoMode::FPS
@brief Frames per second supported by the device at this resolution
*/
VideoMode::VideoMode(int width, int height, int x, int y, int FPS, int format) :
width(width), height(height), x(x), y(y),
FPS(FPS), pixel_format(format)
{
}
VideoMode::VideoMode(QRect rect) :
width(rect.width()), height(rect.height()),
x(rect.x()), y(rect.y()),
FPS(0), pixel_format(0)
{
}
QRect VideoMode::toRect() const
{
return QRect(x, y, width, height);
}
bool VideoMode::operator==(const VideoMode &other) const
{
return width == other.width
&& height == other.height
&& x == other.x
&& y == other.y
&& FPS == other.FPS
&& pixel_format == other.pixel_format;
}
uint32_t VideoMode::norm(const VideoMode &other) const
{
return abs(this->width-other.width) + abs(this->height-other.height);
}
/**
@brief All zeros means a default/unspecified mode
*/
VideoMode::operator bool() const
{
return width || height || FPS;
}

View File

@ -17,60 +17,29 @@
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef VIDEOMODE_H
#define VIDEOMODE_H
#include <QRect>
#include <cstdint>
/// Describes a video mode supported by a device
struct VideoMode
{
unsigned short width, height; ///< Displayed video resolution (NOT frame resolution)
unsigned short x, y; ///< Coordinates of upper-left corner
float FPS; ///< Max frames per second supported by the device at this resolution
unsigned short width, height;
unsigned short x, y;
float FPS;
uint32_t pixel_format;
VideoMode(int width = 0, int height = 0, int x = 0, int y = 0,
int FPS = 0, int format = 0) :
width(width), height(height), x(x), y(y),
FPS(FPS), pixel_format(format)
{
}
int FPS = 0, int format = 0);
VideoMode(QRect rect) :
width(rect.width()), height(rect.height()),
x(rect.x()), y(rect.y()),
FPS(0), pixel_format(0)
{
}
VideoMode(QRect rect);
QRect toRect() const
{
return QRect(x, y, width, height);
}
QRect toRect() const;
/// All zeros means a default/unspecified mode
operator bool() const
{
return width || height || FPS;
}
bool operator==(const VideoMode& other) const
{
return width == other.width
&& height == other.height
&& x == other.x
&& y == other.y
&& FPS == other.FPS
&& pixel_format == other.pixel_format;
}
uint32_t norm(const VideoMode& other) const
{
return abs(this->width-other.width) + abs(this->height-other.height);
}
operator bool() const;
bool operator==(const VideoMode& other) const;
uint32_t norm(const VideoMode& other) const;
};
#endif // VIDEOMODE_H

View File

@ -25,24 +25,37 @@
class VideoFrame;
/// An abstract source of video frames
/// When it has at least one subscriber the source will emit new video frames
/// Subscribing is recursive, multiple users can subscribe to the same VideoSource
/**
@brief An abstract source of video frames
When it has at least one subscriber the source will emit new video frames
Subscribing is recursive, multiple users can subscribe to the same VideoSource
*/
class VideoSource : public QObject
{
Q_OBJECT
public:
virtual ~VideoSource() = default;
/// If subscribe sucessfully opens the source, it will start emitting frameAvailable signals
/**
If subscribe sucessfully opens the source, it will start emitting frameAvailable signals.
*/
virtual bool subscribe() = 0;
/// Stop emitting frameAvailable signals, and free associated resources if necessary
/**
Stop emitting frameAvailable signals, and free associated resources if necessary.
*/
virtual void unsubscribe() = 0;
signals:
/**
Emitted when new frame available to use.
@param frame New frame.
*/
void frameAvailable(std::shared_ptr<VideoFrame> frame);
/// Emitted when the source is stopped for an indefinite amount of time,
/// but might restart sending frames again later
/**
Emitted when the source is stopped for an indefinite amount of time,
but might restart sending frames again later
*/
void sourceStopped();
};

View File

@ -30,6 +30,11 @@
#include <QLabel>
#include <QDebug>
/**
@var std::atomic_bool VideoSurface::frameLock
@brief Fast lock for lastFrame.
*/
float getSizeRatio(const QSize size)
{
return size.width() / static_cast<float>(size.height());
@ -63,6 +68,13 @@ bool VideoSurface::isExpanding() const
return expanding;
}
/**
@brief Update source.
@note nullptr is a valid option.
@param src source to set.
Unsubscribe from old source and subscribe to new.
*/
void VideoSurface::setSource(VideoSource *src)
{
if (source == src)

View File

@ -35,7 +35,7 @@ public:
~VideoSurface();
bool isExpanding() const;
void setSource(VideoSource* src); //NULL is a valid option
void setSource(VideoSource* src);
QRect getBoundingRect() const;
float getRatio() const;
void setAvatar(const QPixmap& pixmap);
@ -65,7 +65,7 @@ private:
QRect boundingRect;
VideoSource* source;
std::shared_ptr<VideoFrame> lastFrame;
std::atomic_bool frameLock; ///< Fast lock for lastFrame
std::atomic_bool frameLock;
uint8_t hasSubscribed;
QPixmap avatar;
float ratio;

View File

@ -89,9 +89,8 @@ void AboutUser::onSelectDirClicked()
}
/**
* @brief AboutUser::onAcceptedClicked When users clicks the bottom OK button,
* save all settings
*/
@brief Called when user clicks the bottom OK button, save all settings
*/
void AboutUser::onAcceptedClicked()
{
ToxId toxId = ToxId(ui->publicKey->text());

View File

@ -41,6 +41,11 @@
#include <QWindow>
#include <QScrollArea>
/**
@var QString AddFriendForm::lastUsername
@brief Cached username so we can retranslate the invite message
*/
AddFriendForm::AddFriendForm()
{
tabWidget = new QTabWidget();

View File

@ -85,7 +85,7 @@ private:
QTextEdit message;
QVBoxLayout layout, headLayout;
QWidget *head, *main;
QString lastUsername; // Cached username so we can retranslate the invite message
QString lastUsername;
QTabWidget* tabWidget;
QVBoxLayout* requestsLayout;
QList<QPushButton*> acceptButtons;

View File

@ -26,10 +26,13 @@
#include <QMenu>
#include "src/core/corestructs.h"
#include "src/chatlog/chatmessage.h"
#include "../../core/toxid.h"
#include "src/core/toxid.h"
// Spacing in px inserted when the author of the last message changes
#define AUTHOR_CHANGE_SPACING 5 // why the hell is this a thing? surely the different font is enough?
/**
Spacing in px inserted when the author of the last message changes
@note Why the hell is this a thing? surely the different font is enough?
*/
#define AUTHOR_CHANGE_SPACING 5
class QLabel;
class QVBoxLayout;

View File

@ -37,6 +37,14 @@
#include <QDragEnterEvent>
#include <QtAlgorithms>
/**
@var QList<QLabel*> GroupChatForm::peerLabels
@brief Maps peernumbers to the QLabels in namesListLayout.
@var QMap<int, QTimer*> GroupChatForm::peerAudioTimers
@brief Timeout = peer stopped sending audio.
*/
GroupChatForm::GroupChatForm(Group* chatGroup)
: group(chatGroup), inCall{false}
{

View File

@ -61,8 +61,8 @@ private:
private:
Group* group;
QList<QLabel*> peerLabels; // maps peernumbers to the QLabels in namesListLayout
QMap<int, QTimer*> peerAudioTimers; // timeout = peer stopped sending audio
QList<QLabel*> peerLabels;
QMap<int, QTimer*> peerAudioTimers;
FlowLayout* namesListLayout;
QLabel *nusersLabel;
TabCompleter* tabber;

View File

@ -20,15 +20,21 @@
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
/* This file was taken from the Quassel IRC client source (src/uisupport), and
was greatly simplified for use in qTox. */
/**
@file tabcompleter.h
@file tabcompleter.cpp
These files were taken from the Quassel IRC client source (src/uisupport), and
was greatly simplified for use in qTox.
*/
#include "tabcompleter.h"
#include <QRegExp>
#include <QKeyEvent>
#include "src/core/core.h"
#include "src/group.h"
#include "src/widget/tool/chattextedit.h"
#include <QRegExp>
#include <QKeyEvent>
const QString TabCompleter::nickSuffix = QString(": ");

View File

@ -20,18 +20,13 @@
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
/* This file was taken from the Quassel IRC client source (src/uisupport), and
was greatly simplified for use in qTox. */
#ifndef TABCOMPLETER_H
#define TABCOMPLETER_H
#include <QString>
#include <QMap>
#include <QObject> // I'm really confused why I need this
class ChatTextEdit;
class Group;
#include "src/group.h"
#include "src/widget/tool/chattextedit.h"
class TabCompleter : public QObject
{

View File

@ -31,6 +31,17 @@
#include <QPushButton>
#include <QThread>
/**
@class GUI
@brief Abstracts the GUI from the target backend (DesktopGUI, ...)
All the functions exposed here are thread-safe.
Prefer calling this class to calling a GUI backend directly.
@fn void GUI::resized()
@brief Emitted when the GUI is resized on supported platforms.
*/
GUI::GUI(QObject *parent) :
QObject(parent)
{
@ -39,6 +50,9 @@ GUI::GUI(QObject *parent) :
connect(Nexus::getDesktopGUI(), &Widget::resized, this, &GUI::resized);
}
/**
@brief Returns the singleton instance.
*/
GUI& GUI::getInstance()
{
static GUI gui;
@ -47,6 +61,9 @@ GUI& GUI::getInstance()
// Implementation of the public clean interface
/**
@brief Clear the GUI's contact list.
*/
void GUI::clearContacts()
{
if (QThread::currentThread() == qApp->thread())
@ -55,6 +72,11 @@ void GUI::clearContacts()
QMetaObject::invokeMethod(&getInstance(), "_clearContacts", Qt::BlockingQueuedConnection);
}
/**
@brief Will enable or disable the GUI.
@note A disabled GUI can't be interacted with by the user.
@param state Enable/disable GUI.
*/
void GUI::setEnabled(bool state)
{
if (QThread::currentThread() == qApp->thread())
@ -68,6 +90,12 @@ void GUI::setEnabled(bool state)
}
}
/**
@brief Change the title of the main window.
@param title Titile to set.
This is usually always visible to the user.
*/
void GUI::setWindowTitle(const QString& title)
{
if (QThread::currentThread() == qApp->thread())
@ -81,6 +109,9 @@ void GUI::setWindowTitle(const QString& title)
}
}
/**
@brief Reloads the application theme and redraw the window.
*/
void GUI::reloadTheme()
{
if (QThread::currentThread() == qApp->thread())
@ -93,6 +124,9 @@ void GUI::reloadTheme()
}
}
/**
@brief Optionally switches to a view of the qTox update being downloaded.
*/
void GUI::showUpdateDownloadProgress()
{
if (QThread::currentThread() == qApp->thread())
@ -105,6 +139,11 @@ void GUI::showUpdateDownloadProgress()
}
}
/**
@brief Show some text to the user.
@param title Title of information window.
@param msg Text in information window.
*/
void GUI::showInfo(const QString& title, const QString& msg)
{
if (QThread::currentThread() == qApp->thread())
@ -118,6 +157,11 @@ void GUI::showInfo(const QString& title, const QString& msg)
}
}
/**
@brief Show a warning to the user
@param title Title of warning window.
@param msg Text in warning window.
*/
void GUI::showWarning(const QString& title, const QString& msg)
{
if (QThread::currentThread() == qApp->thread())
@ -131,6 +175,11 @@ void GUI::showWarning(const QString& title, const QString& msg)
}
}
/**
@brief Show an error to the user.
@param title Title of error window.
@param msg Text in error window.
*/
void GUI::showError(const QString& title, const QString& msg)
{
if (QThread::currentThread() == qApp->thread())
@ -149,6 +198,15 @@ void GUI::showError(const QString& title, const QString& msg)
}
}
/**
@brief Asks the user a question with Ok/Cansel or Yes/No buttons.
@param title Title of question window.
@param msg Text in question window.
@param defaultAns If is true, default was positive answer. Negative otherwise.
@param warning If is true, we will use a special warning style.
@param yesno Show "Yes" and "No" buttons.
@return True if the answer is positive, false otherwise.
*/
bool GUI::askQuestion(const QString& title, const QString& msg,
bool defaultAns, bool warning,
bool yesno)
@ -169,6 +227,18 @@ bool GUI::askQuestion(const QString& title, const QString& msg,
}
}
/**
@brief Asks the user a question.
The text for the displayed buttons can be specified.
@param title Title of question window.
@param msg Text in question window.
@param button1 Text of positive button.
@param button2 Text of negative button.
@param defaultAns If is true, default was positive answer. Negative otherwise.
@param warning If is true, we will use a special warning style.
@return True if the answer is positive, false otherwise.
*/
bool GUI::askQuestion(const QString& title, const QString& msg,
const QString& button1, const QString& button2,
bool defaultAns, bool warning)
@ -188,6 +258,21 @@ bool GUI::askQuestion(const QString& title, const QString& msg,
}
}
/**
@brief Asks the user to input text and returns the answer.
The interface is equivalent to QInputDialog::getItem()
@param parent Is the dialog's parent widget
@param title Is the text which is displayed in the title bar of the dialog.
@param label Is the text which is shown to the user (it should say what should be entered).
@param items Is the string list which is inserted into the combobox.
@param current Is the number of the item which should be the current item.
@param editable If is true the user can enter their own text, otherwise the user may only select one of the existing items.
@param ok If is nonnull will be set to true if the user pressed OK and to false if the user pressed Cancel.
@param flags The dialog will uses to widget flags.
@param hints Is the input method hints that will be used if the combobox is editable and an input method is active.
@return This function returns the text of the current item, or if editable is true, the current text of the combobox.
*/
QString GUI::itemInputDialog(QWidget * parent, const QString & title,
const QString & label, const QStringList & items,
int current, bool editable, bool * ok,
@ -211,6 +296,12 @@ QString GUI::itemInputDialog(QWidget * parent, const QString & title,
}
}
/**
@brief Asks the user to answer a password.
@param cancel Is the text on the cancel button.
@param body Is descriptive text that will be shown to the user.
@return Entered password.
*/
QString GUI::passwordDialog(const QString& cancel, const QString& body)
{
if (QThread::currentThread() == qApp->thread())
@ -369,6 +460,10 @@ QString GUI::_passwordDialog(const QString& cancel, const QString& body)
// Other
/**
@brief Get the main widget.
@return The main QWidget* of the application
*/
QWidget* GUI::getMainWidget()
{
QWidget* maingui{nullptr};

View File

@ -25,69 +25,44 @@
class QWidget;
/// Abstracts the GUI from the target backend (DesktopGUI, ...)
/// All the functions exposed here are thread-safe
/// Prefer calling this class to calling a GUI backend directly
class GUI : public QObject
{
Q_OBJECT
public:
static GUI& getInstance();
/// Returns the main QWidget* of the application
static QWidget* getMainWidget();
/// Clear the GUI's contact list
static void clearContacts();
/// Will enable or disable the GUI.
/// A disabled GUI can't be interacted with by the user
static void setEnabled(bool state);
/// Change the title of the main window
/// This is usually always visible to the user
static void setWindowTitle(const QString& title);
/// Reloads the application theme and redraw the window
static void reloadTheme();
/// Optionally switches to a view of the qTox update being downloaded
static void showUpdateDownloadProgress();
/// Show some text to the user, for example in a message box
static void showInfo(const QString& title, const QString& msg);
/// Show a warning to the user, for example in a message box
static void showWarning(const QString& title, const QString& msg);
/// Show an error to the user, for example in a message box
static void showError(const QString& title, const QString& msg);
/// Asks the user a question, for example in a message box.
/// If warning is true, we will use a special warning style.
/// Returns the answer.
static bool askQuestion(const QString& title, const QString& msg,
bool defaultAns = false, bool warning = true,
bool yesno = true);
/// Asks the user a question, for example in a message box.
/// The text for the displayed buttons can be specified.
/// If warning is true, we will use a special warning style.
/// Returns the answer.
static bool askQuestion(const QString& title, const QString& msg,
const QString& button1, const QString& button2,
bool defaultAns = false, bool warning = true);
/// Asks the user to input text and returns the answer.
/// The interface is equivalent to QInputDialog::getItem()
static QString itemInputDialog(QWidget * parent, const QString & title,
const QString & label, const QStringList & items,
int current = 0, bool editable = true, bool * ok = 0,
Qt::WindowFlags flags = 0,
Qt::InputMethodHints hints = Qt::ImhNone);
/// Asks the user to answer a password
/// cancel is the text on the cancel button and body
/// is descriptive text that will be shown to the user
static QString passwordDialog(const QString& cancel, const QString& body);
signals:
/// Emitted when the GUI is resized on supported platforms
/// Guaranteed to work on desktop platforms
void resized();
private:
explicit GUI(QObject *parent = 0);
// Private implementation, those must be called from the GUI thread
private slots:
// Private implementation, those must be called from the GUI thread
void _clearContacts();
void _setEnabled(bool state);
void _setWindowTitle(const QString& title);

View File

@ -71,6 +71,9 @@ LoginScreen::~LoginScreen()
delete ui;
}
/**
@brief Resets the UI, clears all fields.
*/
void LoginScreen::reset()
{
ui->newUsername->clear();

View File

@ -36,7 +36,7 @@ class LoginScreen : public QWidget
public:
explicit LoginScreen(QWidget *parent = 0);
~LoginScreen();
void reset(); ///< Resets the UI, clears all fields
void reset();
bool event(QEvent* event) final override;

View File

@ -20,8 +20,14 @@
#include "maskablepixmapwidget.h"
#include <QPainter>
/**
@var QPixmap* MaskablePixmapWidget::renderTarget
@brief pointer to dynamically call the constructor.
*/
MaskablePixmapWidget::MaskablePixmapWidget(QWidget *parent, QSize size, QString maskName)
: QWidget(parent)
, renderTarget(nullptr)
, maskName(maskName)
, clickable(false)
{

View File

@ -42,8 +42,8 @@ protected:
virtual void mousePressEvent(QMouseEvent *) final override;
private:
QPixmap pixmap, mask, unscaled; // a lot of memory...
QPixmap* renderTarget = nullptr; // pointer to dynamically call the constructor
QPixmap pixmap, mask, unscaled;
QPixmap* renderTarget;
QSize size;
QString maskName;
bool clickable;

View File

@ -67,16 +67,16 @@ void NotificationScrollArea::trackWidget(GenericChatroomWidget* widget)
}
/**
* @brief Delete notification bar to visible elements on scroll area
*/
@brief Delete notification bar from visible elements on scroll area
*/
void NotificationScrollArea::updateVisualTracking() {
updateTracking(nullptr);
}
/**
* @brief Delete notification bar from visible elements and widget on scroll area
* @param widget Chatroom widget to remove from tracked widgets
*/
@brief Delete notification bar from visible elements and widget on scroll area
@param widget Chatroom widget to remove from tracked widgets
*/
void NotificationScrollArea::updateTracking(GenericChatroomWidget *widget)
{
QHash<GenericChatroomWidget*, Visibility>::iterator i = trackedWidgets.begin();

View File

@ -31,6 +31,11 @@
#include <sys/errno.h>
#endif
/**
@file qrwidget.cpp
@link https://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c
*/
QRWidget::QRWidget(QWidget *parent) : QWidget(parent), data("0")
//Note: The encoding fails with empty string so I just default to something else.
//Use the setQRData() call to change this.
@ -58,10 +63,10 @@ QImage* QRWidget::getImage()
}
/**
* @brief QRWidget::saveImage
* @param path Full path to the file with extension.
* @return indicate if saving was successful.
*/
@brief QRWidget::saveImage
@param path Full path to the file with extension.
@return indicate if saving was successful.
*/
bool QRWidget::saveImage(QString path)
{
return image->save(path, 0, 75); //0 - image format same as file extension, 75-quality, png file is ~6.3kb

View File

@ -21,8 +21,6 @@
#ifndef QRWIDGET_H
#define QRWIDGET_H
// https://stackoverflow.com/questions/21400254/how-to-draw-a-qr-code-with-qt-in-native-c-c
#include <QWidget>
class QRWidget : public QWidget

View File

@ -31,6 +31,31 @@
#include <QSvgRenderer>
#include <QPainter>
/**
@enum Style::Font
@var ExtraBig
@brief [SystemDefault + 2]px, bold
@var Big
@brief [SystemDefault]px
@var BigBold
@brief [SystemDefault]px, bold
@var Medium
@brief [SystemDefault - 1]px
@var MediumBold
@brief [SystemDefault - 1]px, bold
@var Small
@brief [SystemDefault - 2]px
@var SmallLight
@brief [SystemDefault - 2]px, light
*/
// helper functions
QFont appFont(int pixelSize, int weight)
{
@ -181,6 +206,12 @@ void Style::setThemeColor(int color)
setThemeColor(themeColorColors[color]);
}
/**
@brief Set theme color.
@param color Color to set.
Pass an invalid QColor to reset to defaults.
*/
void Style::setThemeColor(const QColor &color)
{
if (!color.isValid())
@ -205,6 +236,9 @@ void Style::setThemeColor(const QColor &color)
dict["@themeLight"] = getColor(ThemeLight).name();
}
/**
@brief Reloads some CCS
*/
void Style::applyTheme()
{
GUI::reloadTheme();

View File

@ -49,13 +49,13 @@ public:
enum Font
{
ExtraBig, // [SystemDefault + 2]px, bold
Big, // [SystemDefault ]px
BigBold, // [SystemDefault ]px, bold
Medium, // [SystemDefault - 1]px
MediumBold, // [SystemDefault - 1]px, bold
Small, // [SystemDefault - 2]px
SmallLight // [SystemDefault - 2]px, light
ExtraBig,
Big,
BigBold,
Medium,
MediumBold,
Small,
SmallLight
};
static QStringList getThemeColorNames();
@ -65,8 +65,8 @@ public:
static QString resolve(QString qss, const QFont& baseFont = QFont());
static void repolish(QWidget* w);
static void setThemeColor(int color);
static void setThemeColor(const QColor &color); ///< Pass an invalid QColor to reset to defaults
static void applyTheme(); ///< Reloads some CCS
static void setThemeColor(const QColor &color);
static void applyTheme();
static QPixmap scaleSvgImage(const QString& path, uint32_t width, uint32_t height);
static QList<QColor> themeColorColors;

Some files were not shown because too many files have changed in this diff Show More