Browse Source

Merge branch 'loginscreen'

Rewrite the profile management code and implement a new login screen.

Fixes #1776
Fixes #1746
Fixes #1596
Fixes #1562
pull/1793/head
tux3 10 years ago
parent
commit
3878f88d06
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
  1. 1
      img/login_logo.svg
  2. 15
      qtox.pro
  3. 1
      res.qrc
  4. 241
      src/core/core.cpp
  5. 46
      src/core/core.h
  6. 259
      src/core/coreencryption.cpp
  7. 12
      src/historykeeper.cpp
  8. 30
      src/main.cpp
  9. 8
      src/misc/db/encrypteddb.cpp
  10. 185
      src/misc/settings.cpp
  11. 25
      src/misc/settings.h
  12. 87
      src/nexus.cpp
  13. 18
      src/nexus.h
  14. 339
      src/profile.cpp
  15. 76
      src/profile.h
  16. 13
      src/profilelocker.cpp
  17. 4
      src/profilelocker.h
  18. 197
      src/widget/form/profileform.cpp
  19. 18
      src/widget/form/profileform.h
  20. 108
      src/widget/form/profileform.ui
  21. 69
      src/widget/form/setpassworddialog.cpp
  22. 1
      src/widget/form/setpassworddialog.h
  23. 6
      src/widget/form/settings/generalform.cpp
  24. 212
      src/widget/form/settings/privacyform.cpp
  25. 4
      src/widget/form/settings/privacyform.h
  26. 85
      src/widget/form/settings/privacysettings.ui
  27. 16
      src/widget/gui.cpp
  28. 3
      src/widget/gui.h
  29. 167
      src/widget/loginscreen.cpp
  30. 33
      src/widget/loginscreen.h
  31. 1109
      src/widget/loginscreen.ui
  32. 2
      src/widget/widget.cpp
  33. 3
      src/widget/widget.h

1
img/login_logo.svg

@ -0,0 +1 @@
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1" xmlns:cc="http://creativecommons.org/ns#" viewBox="0 0 167.96599 185.6131" xmlns:dc="http://purl.org/dc/elements/1.1/"><g fill="#fff"><g transform="translate(-120.433,76.87011)"><path fill="#fff" d="m120.434 41.477h49.389v13.332h-17.371v53.934h-14.646v-53.934h-17.373v-13.332z"/></g><g transform="translate(-120.433,76.87011)"><path fill="#fff" d="m226.131 101.977c-0.574 1.348-1.363 2.524-2.373 3.534-1.012 1.011-2.189 1.804-3.535 2.374-1.349 0.573-2.795 0.857-4.344 0.857h-24.847c-1.55 0-2.997-0.284-4.343-0.857-1.348-0.57-2.525-1.363-3.535-2.374-1.01-1.01-1.803-2.187-2.373-3.534-0.573-1.347-0.857-2.794-0.857-4.344v-45.045c0-1.548 0.284-2.996 0.857-4.343 0.57-1.346 1.363-2.525 2.373-3.535s2.188-1.8 3.535-2.374c1.346-0.571 2.793-0.858 4.343-0.858h24.847c1.549 0 2.995 0.287 4.344 0.858 1.346 0.573 2.523 1.364 3.535 2.374 1.01 1.01 1.799 2.189 2.373 3.535 0.57 1.348 0.857 2.795 0.857 4.343v45.045c0.001 1.55-0.286 2.997-0.857 4.344zm-14.798-47.168h-15.756c-0.674 0-1.011 0.338-1.011 1.01v38.582c0 0.674 0.337 1.01 1.011 1.01h15.756c0.673 0 1.011-0.336 1.011-1.01v-38.582c0-0.672-0.338-1.01-1.011-1.01z"/></g><g transform="translate(-120.433,76.87011)"><path fill="#fff" d="m261.834 61.576 10.707-20.099h15.352l-17.775 33.129 18.281 34.137h-15.354l-11.211-21.108-11.211 21.108h-15.352l18.281-34.137-17.775-33.129h15.352l10.705 20.099z"/></g><g transform="translate(38.292389,1.5964409e-4)"><path fill="#fff" d="m71.574 41.851c0-4.685 0.074-10.553-0.033-15.237-0.047-2.018-0.268-4.076-0.746-6.034-3.421-13.999-16.972-22.705-31.201-20.13-12.596 2.279-21.973 13.518-21.973 26.336v15.035c-0.475 0-0.794-0.001-1.116 0h-11.482c-2.773 0-5.023 2.249-5.023 5.023v56.875c0 2.773 2.25 5.023 5.023 5.023h79.451c2.775 0 5.024-2.25 5.024-5.023v-56.875c0-2.774-2.249-5.023-5.024-5.023l-12.9 0.03zm-19.467 53.267h-14.775c-2.322 0-4.203-1.914-4.203-4.274 0-0.063 0.007-0.127 0.009-0.19-0.005 0-0.006-1.021-0.008-2.041-0.001-1.129 0.511-4.289 2.061-6.574 1.641-2.418 3.855-3.99 6.257-4.697-3.166-1.291-5.396-4.398-5.396-8.025 0-4.789 3.88-8.668 8.667-8.668 4.789 0 8.667 3.879 8.667 8.668 0 3.719-2.344 6.891-5.637 8.12 2.332 0.753 4.537 2.313 6.273 4.707 1.621 2.235 2.285 5.343 2.285 6.47v2.043c-0.006 0.062 0 0.125 0 0.189 0.001 2.358-1.88 4.272-4.2 4.272m9.672-62.014c-1.562 4.611-4.52 8.305-7.61 11.912-2.386 2.783-5.19 5.125-8.176 7.247-0.144 0.103-0.298 0.188-0.685 0.427 1.781-3.076 3.217-5.998 3.633-9.404-4.234 1.002-8.227 0.564-12.093-1.14-6.862-3.023-10.963-9.618-10.263-17.063 0.573-6.127 3.94-10.543 9.321-13.395 8.667-4.593 19.734-1.655 24.689 6.516 2.887 4.758 2.932 9.741 1.184 14.9"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

15
qtox.pro

@ -32,7 +32,8 @@ FORMS += \
src/widget/form/setpassworddialog.ui \ src/widget/form/setpassworddialog.ui \
src/chatlog/content/filetransferwidget.ui \ src/chatlog/content/filetransferwidget.ui \
src/widget/form/settings/advancedsettings.ui \ src/widget/form/settings/advancedsettings.ui \
src/android.ui src/android.ui \
src/widget/loginscreen.ui
CONFIG += c++11 CONFIG += c++11
@ -358,7 +359,8 @@ contains(ENABLE_SYSTRAY_GTK_BACKEND, NO) {
src/widget/callconfirmwidget.h \ src/widget/callconfirmwidget.h \
src/widget/systemtrayicon.h \ src/widget/systemtrayicon.h \
src/misc/qrwidget.h \ src/misc/qrwidget.h \
src/widget/systemtrayicon_private.h src/widget/systemtrayicon_private.h \
src/widget/loginscreen.h
SOURCES += \ SOURCES += \
src/widget/form/addfriendform.cpp \ src/widget/form/addfriendform.cpp \
@ -418,7 +420,8 @@ contains(ENABLE_SYSTRAY_GTK_BACKEND, NO) {
src/chatlog/pixmapcache.cpp \ src/chatlog/pixmapcache.cpp \
src/offlinemsgengine.cpp \ src/offlinemsgengine.cpp \
src/misc/qrwidget.cpp \ src/misc/qrwidget.cpp \
src/widget/genericchatroomwidget.cpp src/widget/genericchatroomwidget.cpp \
src/widget/loginscreen.cpp
} }
win32 { win32 {
@ -467,7 +470,8 @@ SOURCES += \
src/video/cameradevice.cpp \ src/video/cameradevice.cpp \
src/video/camerasource.cpp \ src/video/camerasource.cpp \
src/video/corevideosource.cpp \ src/video/corevideosource.cpp \
src/core/toxid.cpp src/core/toxid.cpp \
src/profile.cpp
HEADERS += \ HEADERS += \
src/audio.h \ src/audio.h \
@ -500,4 +504,5 @@ HEADERS += \
src/video/camerasource.h \ src/video/camerasource.h \
src/video/corevideosource.h \ src/video/corevideosource.h \
src/video/videomode.h \ src/video/videomode.h \
src/core/toxid.h src/core/toxid.h \
src/profile.h

1
res.qrc

@ -119,5 +119,6 @@
<file>ui/acceptCall/acceptCall.svg</file> <file>ui/acceptCall/acceptCall.svg</file>
<file>ui/rejectCall/rejectCall.svg</file> <file>ui/rejectCall/rejectCall.svg</file>
<file>ui/volButton/volButtonDisabled.png</file> <file>ui/volButton/volButtonDisabled.png</file>
<file>img/login_logo.svg</file>
</qresource> </qresource>
</RCC> </RCC>

241
src/core/core.cpp

@ -24,11 +24,13 @@
#include "src/audio.h" #include "src/audio.h"
#include "src/profilelocker.h" #include "src/profilelocker.h"
#include "src/avatarbroadcaster.h" #include "src/avatarbroadcaster.h"
#include "src/profile.h"
#include "corefile.h" #include "corefile.h"
#include <tox/tox.h> #include <tox/tox.h>
#include <ctime> #include <ctime>
#include <cassert>
#include <limits> #include <limits>
#include <functional> #include <functional>
@ -52,19 +54,15 @@ QThread* Core::coreThread{nullptr};
#define MAX_GROUP_MESSAGE_LEN 1024 #define MAX_GROUP_MESSAGE_LEN 1024
Core::Core(QThread *CoreThread, QString loadPath) : Core::Core(QThread *CoreThread, Profile& profile) :
tox(nullptr), toxav(nullptr), loadPath(loadPath), ready{false} tox(nullptr), toxav(nullptr), profile(profile), ready{false}
{ {
qDebug() << "loading Tox from" << loadPath;
coreThread = CoreThread; coreThread = CoreThread;
Audio::getInstance(); Audio::getInstance();
videobuf = nullptr; videobuf = nullptr;
encryptionKey = nullptr;
for (int i = 0; i < ptCounter; i++)
pwsaltedkeys[i] = nullptr;
toxTimer = new QTimer(this); toxTimer = new QTimer(this);
toxTimer->setSingleShot(true); toxTimer->setSingleShot(true);
@ -104,8 +102,14 @@ Core::~Core()
{ {
qDebug() << "Deleting Core"; qDebug() << "Deleting Core";
saveConfiguration(); if (coreThread->isRunning())
toxTimer->stop(); {
if (QThread::currentThread() == coreThread)
killTimers(false);
else
QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection,
Q_ARG(bool, false));
}
coreThread->exit(0); coreThread->exit(0);
while (coreThread->isRunning()) while (coreThread->isRunning())
{ {
@ -127,7 +131,7 @@ Core* Core::getInstance()
return Nexus::getCore(); return Nexus::getCore();
} }
void Core::make_tox(QByteArray savedata) void Core::makeTox(QByteArray savedata)
{ {
// IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options. // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options.
bool enableIPv6 = Settings::getInstance().getEnableIPv6(); bool enableIPv6 = Settings::getInstance().getEnableIPv6();
@ -237,19 +241,25 @@ void Core::make_tox(QByteArray savedata)
void Core::start() void Core::start()
{ {
qDebug() << "Starting up"; bool isNewProfile = profile.isNewProfile();
if (isNewProfile)
QByteArray savedata = loadToxSave(loadPath);
make_tox(savedata);
// Do we need to create a new save & profile?
if (savedata.isNull())
{ {
qDebug() << "Save file not found, creating a new profile"; qDebug() << "Creating a new profile";
makeTox(QByteArray());
Settings::getInstance().load(); Settings::getInstance().load();
setStatusMessage(tr("Toxing on qTox")); setStatusMessage(tr("Toxing on qTox"));
setUsername(tr("qTox User")); setUsername(profile.getName());
}
else
{
qDebug() << "Loading user profile";
QByteArray savedata = profile.loadToxSave();
if (savedata.isEmpty())
{
emit failedToStart();
return;
}
makeTox(savedata);
} }
qsrand(time(nullptr)); qsrand(time(nullptr));
@ -275,7 +285,7 @@ void Core::start()
emit idSet(id); emit idSet(id);
// tox core is already decrypted // tox core is already decrypted
if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs()) if (Settings::getInstance().getEnableLogging() && Nexus::getProfile()->isEncrypted())
checkEncryptedHistory(); checkEncryptedHistory();
loadFriends(); loadFriends();
@ -324,7 +334,7 @@ void Core::start()
} }
else else
{ {
qDebug() << "Error loading self avatar"; qDebug() << "Self avatar not found";
} }
ready = true; ready = true;
@ -332,9 +342,9 @@ void Core::start()
// If we created a new profile earlier, // If we created a new profile earlier,
// now that we're ready save it and ONLY THEN broadcast the new ID. // now that we're ready save it and ONLY THEN broadcast the new ID.
// This is useful for e.g. the profileForm that searches for saves. // This is useful for e.g. the profileForm that searches for saves.
if (savedata.isNull()) if (isNewProfile)
{ {
saveConfiguration(); profile.saveToxSave();
emit idSet(getSelfId().toString()); emit idSet(getSelfId().toString());
} }
@ -577,7 +587,7 @@ void Core::acceptFriendRequest(const QString& userId)
} }
else else
{ {
saveConfiguration(); profile.saveToxSave();
emit friendAdded(friendId, userId); emit friendAdded(friendId, userId);
} }
} }
@ -622,7 +632,7 @@ void Core::requestFriendship(const QString& friendAddress, const QString& messag
emit friendAdded(friendId, userId); emit friendAdded(friendId, userId);
} }
} }
saveConfiguration(); profile.saveToxSave();
} }
int Core::sendMessage(uint32_t friendId, const QString& message) int Core::sendMessage(uint32_t friendId, const QString& message)
@ -737,7 +747,7 @@ void Core::removeFriend(uint32_t friendId, bool fake)
} }
else else
{ {
saveConfiguration(); profile.saveToxSave();
emit friendRemoved(friendId); emit friendRemoved(friendId);
} }
} }
@ -775,7 +785,8 @@ void Core::setUsername(const QString& username)
else else
{ {
emit usernameSet(username); emit usernameSet(username);
saveConfiguration(); if (ready)
profile.saveToxSave();
} }
} }
@ -839,7 +850,8 @@ void Core::setStatusMessage(const QString& message)
} }
else else
{ {
saveConfiguration(); if (ready)
profile.saveToxSave();
emit statusMessageSet(message); emit statusMessageSet(message);
} }
} }
@ -864,7 +876,7 @@ void Core::setStatus(Status status)
} }
tox_self_set_status(tox, userstatus); tox_self_set_status(tox, userstatus);
saveConfiguration(); profile.saveToxSave();
emit statusSet(status); emit statusSet(status);
} }
@ -885,151 +897,15 @@ QString Core::sanitize(QString name)
return name; return name;
} }
QByteArray Core::loadToxSave(QString path) QByteArray Core::getToxSaveData()
{ {
uint32_t fileSize = tox_get_savedata_size(tox);
QByteArray data; QByteArray data;
loadPath = ""; // if not empty upon return, then user forgot a password and is switching data.resize(fileSize);
tox_get_savedata(tox, (uint8_t*)data.data());
// If we can't get a lock, then another instance is already using that profile
while (!ProfileLocker::lock(QFileInfo(path).baseName()))
{
qWarning() << "Profile "<<QFileInfo(path).baseName()<<" is already in use, pick another";
GUI::showWarning(tr("Profile already in use"),
tr("This profile is already used by another qTox instance\n"
"Please select another profile"));
QString tmppath = Settings::getInstance().askProfiles();
if (tmppath.isEmpty())
continue;
Settings::getInstance().switchProfile(tmppath);
path = QDir(Settings::getSettingsDirPath()).filePath(tmppath + TOX_EXT);
HistoryKeeper::resetInstance();
}
QFile configurationFile(path);
qDebug() << "loadConfiguration: reading from " << path;
if (!configurationFile.exists())
{
qWarning() << "The Tox configuration file "<<path<<" was not found";
return data;
}
if (!configurationFile.open(QIODevice::ReadOnly))
{
qCritical() << "File " << path << " cannot be opened";
return data;
}
qint64 fileSize = configurationFile.size();
if (fileSize > 0)
{
data = configurationFile.readAll();
if (tox_is_data_encrypted((uint8_t*)data.data()))
{
if (!loadEncryptedSave(data))
{
configurationFile.close();
QString profile;
do {
profile = Settings::getInstance().askProfiles();
} while (profile.isEmpty());
if (!profile.isEmpty())
{
Settings::getInstance().switchProfile(profile);
HistoryKeeper::resetInstance();
return loadToxSave(QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT));
}
return QByteArray();
}
}
}
configurationFile.close();
return data; return data;
} }
void Core::saveConfiguration()
{
if (QThread::currentThread() != coreThread)
return (void) QMetaObject::invokeMethod(this, "saveConfiguration");
if (!isReady())
return;
ProfileLocker::assertLock();
QString dir = Settings::getSettingsDirPath();
QDir directory(dir);
if (!directory.exists() && !directory.mkpath(directory.absolutePath()))
{
qCritical() << "Error while creating directory " << dir;
return;
}
QString profile = Settings::getInstance().getCurrentProfile();
if (profile == "")
{ // no profile active; this should only happen on startup, if at all
profile = sanitize(getUsername());
if (profile == "") // happens on creation of a new Tox ID
profile = getIDString();
Settings::getInstance().switchProfile(profile);
}
QString path = directory.filePath(profile + TOX_EXT);
saveConfiguration(path);
}
void Core::switchConfiguration(const QString& _profile)
{
QString profile = QFileInfo(_profile).baseName();
// If we can't get a lock, then another instance is already using that profile
while (!profile.isEmpty() && !ProfileLocker::lock(profile))
{
qWarning() << "Profile "<<profile<<" is already in use, pick another";
GUI::showWarning(tr("Profile already in use"),
tr("This profile is already used by another qTox instance\n"
"Please select another profile"));
do {
profile = Settings::getInstance().askProfiles();
} while (profile.isEmpty());
}
if (profile.isEmpty())
qDebug() << "creating new Id";
else
qDebug() << "switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile;
saveConfiguration();
saveCurrentInformation(); // part of a hack, see core.h
ready = false;
GUI::setEnabled(false);
clearPassword(ptMain);
clearPassword(ptHistory);
toxTimer->stop();
deadifyTox();
emit selfAvatarChanged(QPixmap(":/img/contact_dark.svg"));
emit blockingClearContacts(); // we need this to block, but signals are required for thread safety
if (profile.isEmpty())
loadPath = "";
else
loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT);
Settings::getInstance().switchProfile(profile);
HistoryKeeper::resetInstance();
start();
}
void Core::loadFriends() void Core::loadFriends()
{ {
const uint32_t friendCount = tox_self_get_friend_list_size(tox); const uint32_t friendCount = tox_self_get_friend_list_size(tox);
@ -1369,3 +1245,28 @@ void Core::resetCallSources()
} }
} }
} }
void Core::killTimers(bool onlyStop)
{
assert(QThread::currentThread() == coreThread);
toxTimer->stop();
if (!onlyStop)
{
delete toxTimer;
toxTimer = nullptr;
}
}
void Core::reset()
{
assert(QThread::currentThread() == coreThread);
ready = false;
killTimers(true);
deadifyTox();
emit selfAvatarChanged(QPixmap(":/img/contact_dark.svg"));
GUI::clearContacts();
start();
}

46
src/core/core.h

@ -29,6 +29,7 @@
#include "coredefines.h" #include "coredefines.h"
#include "toxid.h" #include "toxid.h"
class Profile;
template <typename T> class QList; template <typename T> class QList;
class QTimer; class QTimer;
class QString; class QString;
@ -43,9 +44,7 @@ class Core : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum PasswordType {ptMain = 0, ptHistory, ptCounter}; explicit Core(QThread* coreThread, Profile& profile);
explicit Core(QThread* coreThread, QString initialLoadPath);
static Core* getInstance(); ///< Returns the global widget's Core instance static Core* getInstance(); ///< Returns the global widget's Core instance
~Core(); ~Core();
@ -81,19 +80,18 @@ public:
VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source
static bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active) static bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
bool isPasswordSet(PasswordType passtype); bool isPasswordSet();
bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first
void resetCallSources(); ///< Forces to regenerate each call's audio sources void resetCallSources(); ///< Forces to regenerate each call's audio sources
public slots: public slots:
void start(); ///< Initializes the core, must be called before anything else 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 process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer
void bootstrapDht(); ///< Connects us to the Tox network void bootstrapDht(); ///< Connects us to the Tox network
void saveConfiguration(); QByteArray getToxSaveData(); ///< Returns the unencrypted tox save data
void saveConfiguration(const QString& path);
void switchConfiguration(const QString& profile); ///< Load a different profile and restart the core
void acceptFriendRequest(const QString& userId); void acceptFriendRequest(const QString& userId);
void requestFriendship(const QString& friendAddress, const QString& message); void requestFriendship(const QString& friendAddress, const QString& message);
@ -146,16 +144,14 @@ public slots:
static bool isGroupCallMicEnabled(int groupId); static bool isGroupCallMicEnabled(int groupId);
static bool isGroupCallVolEnabled(int groupId); static bool isGroupCallVolEnabled(int groupId);
void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr); void setPassword(const QString &password, uint8_t* salt = nullptr);
void useOtherPassword(PasswordType type); void clearPassword();
void clearPassword(PasswordType passtype); QByteArray encryptData(const QByteArray& data);
QByteArray encryptData(const QByteArray& data, PasswordType passtype); QByteArray decryptData(const QByteArray& data);
QByteArray decryptData(const QByteArray& data, PasswordType passtype);
signals: signals:
void connected(); void connected();
void disconnected(); void disconnected();
void blockingClearContacts();
void friendRequestReceived(const QString& userId, const QString& message); void friendRequestReceived(const QString& userId, const QString& message);
void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction); void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction);
@ -278,22 +274,22 @@ private:
bool checkConnection(); bool checkConnection();
QByteArray loadToxSave(QString path);
bool loadEncryptedSave(QByteArray& data);
void checkEncryptedHistory(); void checkEncryptedHistory();
void make_tox(QByteArray savedata); void makeTox(QByteArray savedata);
void loadFriends(); void loadFriends();
void checkLastOnline(uint32_t friendId); void checkLastOnline(uint32_t friendId);
void deadifyTox(); void deadifyTox();
private slots:
void killTimers(bool onlyStop); ///< Must only be called from the Core thread
private: private:
Tox* tox; Tox* tox;
ToxAv* toxav; ToxAv* toxav;
QTimer *toxTimer, *fileTimer; //, *saveTimer; QTimer *toxTimer;
QString loadPath; // meaningless after start() is called Profile& profile;
int dhtServerId;
static ToxCall calls[TOXAV_MAX_CALLS]; static ToxCall calls[TOXAV_MAX_CALLS];
#ifdef QTOX_FILTER_AUDIO #ifdef QTOX_FILTER_AUDIO
static AudioFilterer * filterer[TOXAV_MAX_CALLS]; static AudioFilterer * filterer[TOXAV_MAX_CALLS];
@ -302,17 +298,7 @@ private:
QMutex messageSendMutex; QMutex messageSendMutex;
bool ready; bool ready;
TOX_PASS_KEY* pwsaltedkeys[PasswordType::ptCounter] = {nullptr}; // use the pw's hash as the "pw" TOX_PASS_KEY* encryptionKey = nullptr; // use the pw's hash as the "pw"
// Hack for reloading current profile if switching to an encrypted one fails.
// Testing the passwords before killing the current profile is perfectly doable,
// however it would require major refactoring;
// the Core class as a whole also requires major refactoring (especially to support multiple IDs at once),
// so I'm punting on this until then, when it would get fixed anyways
TOX_PASS_KEY* backupkeys[PasswordType::ptCounter] = {nullptr};
QString* backupProfile = nullptr;
void saveCurrentInformation();
QString loadOldInformation();
static const int videobufsize; static const int videobufsize;
static uint8_t* videobuf; static uint8_t* videobuf;

259
src/core/coreencryption.cpp

@ -21,6 +21,8 @@
#include "src/misc/settings.h" #include "src/misc/settings.h"
#include "src/misc/cstring.h" #include "src/misc/cstring.h"
#include "src/historykeeper.h" #include "src/historykeeper.h"
#include "src/nexus.h"
#include "src/profile.h"
#include <tox/tox.h> #include <tox/tox.h>
#include <tox/toxencryptsave.h> #include <tox/toxencryptsave.h>
#include <QApplication> #include <QApplication>
@ -31,113 +33,67 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt) void Core::setPassword(const QString& password, uint8_t* salt)
{ {
clearPassword(passtype); clearPassword();
if (password.isEmpty()) if (password.isEmpty())
return; return;
pwsaltedkeys[passtype] = new TOX_PASS_KEY; encryptionKey = new TOX_PASS_KEY;
CString str(password); CString str(password);
if (salt) if (salt)
tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype], nullptr); tox_derive_key_with_salt(str.data(), str.size(), salt, encryptionKey, nullptr);
else else
tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype], nullptr); tox_derive_key_from_pass(str.data(), str.size(), encryptionKey, nullptr);
password.clear();
}
void Core::useOtherPassword(PasswordType type)
{
clearPassword(type);
pwsaltedkeys[type] = new TOX_PASS_KEY;
PasswordType other = (type == ptMain) ? ptHistory : ptMain;
std::copy(pwsaltedkeys[other], pwsaltedkeys[other]+1, pwsaltedkeys[type]);
} }
void Core::clearPassword(PasswordType passtype) void Core::clearPassword()
{ {
delete pwsaltedkeys[passtype]; delete encryptionKey;
pwsaltedkeys[passtype] = nullptr; encryptionKey = nullptr;
}
// part of a hack, see core.h
void Core::saveCurrentInformation()
{
if (pwsaltedkeys[ptMain])
{
backupkeys[ptMain] = new TOX_PASS_KEY;
std::copy(pwsaltedkeys[ptMain], pwsaltedkeys[ptMain]+1, backupkeys[ptMain]);
}
if (pwsaltedkeys[ptHistory])
{
backupkeys[ptHistory] = new TOX_PASS_KEY;
std::copy(pwsaltedkeys[ptHistory], pwsaltedkeys[ptHistory]+1, backupkeys[ptHistory]);
}
backupProfile = new QString(Settings::getInstance().getCurrentProfile());
} }
QString Core::loadOldInformation() QByteArray Core::encryptData(const QByteArray& data)
{ {
QString out; if (!encryptionKey)
if (backupProfile)
{ {
out = *backupProfile; qWarning() << "No encryption key set";
delete backupProfile;
backupProfile = nullptr;
}
backupProfile = nullptr;
clearPassword(ptMain);
clearPassword(ptHistory);
// we can just copy the pointer, as long as we null out backupkeys
// (if backupkeys was null anyways, then this is a null-op)
pwsaltedkeys[ptMain] = backupkeys[ptMain];
pwsaltedkeys[ptHistory] = backupkeys[ptHistory];
backupkeys[ptMain] = nullptr;
backupkeys[ptHistory] = nullptr;
return out;
}
QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype)
{
if (!pwsaltedkeys[passtype])
return QByteArray(); return QByteArray();
}
uint8_t encrypted[data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; uint8_t encrypted[data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH];
if (!tox_pass_key_encrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(), if (!tox_pass_key_encrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
pwsaltedkeys[passtype], encrypted, nullptr)) encryptionKey, encrypted, nullptr))
{ {
qWarning() << "encryptData: encryption failed"; qWarning() << "Encryption failed";
return QByteArray(); return QByteArray();
} }
return QByteArray(reinterpret_cast<char*>(encrypted), data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH); return QByteArray(reinterpret_cast<char*>(encrypted), data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH);
} }
QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype) QByteArray Core::decryptData(const QByteArray& data)
{ {
if (!pwsaltedkeys[passtype]) if (!encryptionKey)
{
qWarning() << "No encryption key set";
return QByteArray(); return QByteArray();
}
int sz = data.size() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; int sz = data.size() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
uint8_t decrypted[sz]; uint8_t decrypted[sz];
if (!tox_pass_key_decrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(), if (!tox_pass_key_decrypt(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
pwsaltedkeys[passtype], decrypted, nullptr)) encryptionKey, decrypted, nullptr))
{ {
qWarning() << "decryptData: decryption failed"; qWarning() << "Decryption failed";
return QByteArray(); return QByteArray();
} }
return QByteArray(reinterpret_cast<char*>(decrypted), sz); return QByteArray(reinterpret_cast<char*>(decrypted), sz);
} }
bool Core::isPasswordSet(PasswordType passtype) bool Core::isPasswordSet()
{ {
if (pwsaltedkeys[passtype]) return static_cast<bool>(encryptionKey);
return true;
return false;
} }
QByteArray Core::getSaltFromFile(QString filename) QByteArray Core::getSaltFromFile(QString filename)
@ -163,110 +119,33 @@ QByteArray Core::getSaltFromFile(QString filename)
return res; return res;
} }
bool Core::loadEncryptedSave(QByteArray& data)
{
if (!Settings::getInstance().getEncryptTox())
GUI::showWarning(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless."));
size_t fileSize = data.size();
int error = -1;
QString a(tr("Please enter the password for the %1 profile.", "used in load() when no pw is already set").arg(Settings::getInstance().getCurrentProfile()));
QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()"));
QString dialogtxt;
if (pwsaltedkeys[ptMain]) // password set, try it
{
QByteArray newData(fileSize-TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0);
if (tox_pass_key_decrypt((uint8_t*)data.data(), fileSize, pwsaltedkeys[ptMain],
(uint8_t*)newData.data(), nullptr))
{
data = newData;
Settings::getInstance().setEncryptTox(true);
return true;
}
dialogtxt = tr("The profile password failed. Please try another?", "used only when pw set before load() doesn't work");
}
else
{
dialogtxt = a;
}
uint8_t salt[TOX_PASS_SALT_LENGTH];
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
do
{
QString pw = GUI::passwordDialog(tr("Change profile"), dialogtxt);
if (pw.isEmpty())
{
clearPassword(ptMain);
return false;
}
else
{
setPassword(pw, ptMain, salt);
}
QByteArray newData(fileSize-TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0);
error = !tox_pass_key_decrypt((uint8_t*)data.data(), data.size(), pwsaltedkeys[ptMain],
(uint8_t*)newData.data(), nullptr);
if (!error)
data = newData;
dialogtxt = a + "\n" + b;
} while (error != 0);
Settings::getInstance().setEncryptTox(true);
return true;
}
void Core::checkEncryptedHistory() void Core::checkEncryptedHistory()
{ {
QString path = HistoryKeeper::getHistoryPath(); QString path = HistoryKeeper::getHistoryPath();
bool exists = QFile::exists(path); bool exists = QFile::exists(path) && QFile(path).size()>0;
QByteArray salt = getSaltFromFile(path); QByteArray salt = getSaltFromFile(path);
if (exists && salt.size() == 0) if (exists && salt.size() == 0)
{ // maybe we should handle this better { // maybe we should handle this better
GUI::showWarning(tr("Encrypted chat history"), tr("No encrypted chat history file found, or it was corrupted.\nHistory will be disabled!")); GUI::showWarning(tr("Encrypted chat history"), tr("No encrypted chat history file found, or it was corrupted.\nHistory will be disabled!"));
Settings::getInstance().setEncryptLogs(false);
Settings::getInstance().setEnableLogging(false);
HistoryKeeper::resetInstance(); HistoryKeeper::resetInstance();
return; return;
} }
QString a(tr("Please enter the password for the chat history for the %1 profile.", "used in load() when no hist pw set").arg(Settings::getInstance().getCurrentProfile())); setPassword(Nexus::getProfile()->getPassword(), reinterpret_cast<uint8_t*>(salt.data()));
QString a(tr("Please enter the password for the chat history for the profile \"%1\".", "used in load() when no hist pw set").arg(Nexus::getProfile()->getName()));
QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()")); QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()"));
QString c(tr("\nDisabling chat history now will leave the encrypted history intact (but not usable); if you later remember the password, you may re-enable encryption from the Privacy tab with the correct password to use the history.", "part of history password dialog")); QString c(tr("\nDisabling chat history now will leave the encrypted history intact (but not usable); if you later remember the password, you may re-enable encryption from the Privacy tab with the correct password to use the history.", "part of history password dialog"));
QString dialogtxt; QString dialogtxt;
if (pwsaltedkeys[ptHistory])
{
if (!exists || HistoryKeeper::checkPassword())
return;
dialogtxt = tr("The chat history password failed. Please try another?", "used only when pw set before load() doesn't work"); if (!exists || HistoryKeeper::checkPassword())
} return;
else
{
dialogtxt = a;
}
dialogtxt = tr("The chat history password failed. Please try another?", "used only when pw set before load() doesn't work");
dialogtxt += "\n" + c; dialogtxt += "\n" + c;
if (pwsaltedkeys[ptMain])
{
useOtherPassword(ptHistory);
if (!exists || HistoryKeeper::checkPassword())
{
qDebug() << "using main password for chat history";
return;
}
clearPassword(ptHistory);
}
bool error = true; bool error = true;
do do
{ {
@ -274,87 +153,17 @@ void Core::checkEncryptedHistory()
if (pw.isEmpty()) if (pw.isEmpty())
{ {
clearPassword(ptHistory); clearPassword();
Settings::getInstance().setEncryptLogs(false);
Settings::getInstance().setEnableLogging(false); Settings::getInstance().setEnableLogging(false);
HistoryKeeper::resetInstance(); HistoryKeeper::resetInstance();
return; return;
} }
else else
{ {
setPassword(pw, ptHistory, reinterpret_cast<uint8_t*>(salt.data())); setPassword(pw, reinterpret_cast<uint8_t*>(salt.data()));
} }
error = exists && !HistoryKeeper::checkPassword(); error = exists && !HistoryKeeper::checkPassword();
dialogtxt = a + "\n" + c + "\n" + b; dialogtxt = a + "\n" + c + "\n" + b;
} while (error); } while (error);
} }
void Core::saveConfiguration(const QString& path)
{
if (QThread::currentThread() != coreThread)
return (void) QMetaObject::invokeMethod(this, "saveConfiguration", Q_ARG(const QString&, path));
if (!isReady())
{
qWarning() << "saveConfiguration: Tox not started, aborting!";
return;
}
QSaveFile configurationFile(path);
if (!configurationFile.open(QIODevice::WriteOnly))
{
qCritical() << "File " << path << " cannot be opened";
return;
}
qDebug() << "writing tox_save to " << path;
uint32_t fileSize = tox_get_savedata_size(tox);
bool encrypt = Settings::getInstance().getEncryptTox();
if (fileSize > 0 && fileSize <= std::numeric_limits<int32_t>::max())
{
uint8_t *data = new uint8_t[fileSize];
if (encrypt)
{
if (!pwsaltedkeys[ptMain])
{
// probably zero chance event
GUI::showWarning(tr("NO Password"), tr("Local file encryption is enabled, but there is no password! It will be disabled."));
Settings::getInstance().setEncryptTox(false);
tox_get_savedata(tox, data);
}
else
{
tox_get_savedata(tox, data);
uint8_t* newData = new uint8_t[fileSize+TOX_PASS_ENCRYPTION_EXTRA_LENGTH];
if (tox_pass_key_encrypt(data, fileSize, pwsaltedkeys[ptMain], newData, nullptr))
{
delete[] data;
data = newData;
fileSize+=TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
}
else
{
delete[] newData;
delete[] data;
qCritical() << "Core::saveConfiguration(QString): Encryption failed, couldn't save";
configurationFile.cancelWriting();
return;
}
}
}
else
{
tox_get_savedata(tox, data);
}
configurationFile.write(reinterpret_cast<char *>(data), fileSize);
configurationFile.commit();
delete[] data;
}
Settings::getInstance().save();
}

12
src/historykeeper.cpp

@ -15,6 +15,8 @@
#include "historykeeper.h" #include "historykeeper.h"
#include "misc/settings.h" #include "misc/settings.h"
#include "src/core/core.h" #include "src/core/core.h"
#include "src/nexus.h"
#include "src/profile.h"
#include <QSqlError> #include <QSqlError>
#include <QFile> #include <QFile>
@ -45,9 +47,7 @@ HistoryKeeper *HistoryKeeper::getInstance()
if (Settings::getInstance().getEnableLogging()) if (Settings::getInstance().getEnableLogging())
{ {
bool encrypted = Settings::getInstance().getEncryptLogs(); if (Nexus::getProfile()->isEncrypted())
if (encrypted)
{ {
path = getHistoryPath(); path = getHistoryPath();
dbIntf = new EncryptedDb(path, initLst); dbIntf = new EncryptedDb(path, initLst);
@ -73,8 +73,8 @@ bool HistoryKeeper::checkPassword(int encrypted)
if (!Settings::getInstance().getEnableLogging() && (encrypted == -1)) if (!Settings::getInstance().getEnableLogging() && (encrypted == -1))
return true; return true;
if ((encrypted == 1) || (encrypted == -1 && Settings::getInstance().getEncryptLogs())) if ((encrypted == 1) || (encrypted == -1 && Nexus::getProfile()->isEncrypted()))
return EncryptedDb::check(getHistoryPath(Settings::getInstance().getCurrentProfile(), encrypted)); return EncryptedDb::check(getHistoryPath(Nexus::getProfile()->getName(), encrypted));
return true; return true;
} }
@ -363,7 +363,7 @@ QString HistoryKeeper::getHistoryPath(QString currentProfile, int encrypted)
if (currentProfile.isEmpty()) if (currentProfile.isEmpty())
currentProfile = Settings::getInstance().getCurrentProfile(); currentProfile = Settings::getInstance().getCurrentProfile();
if (encrypted == 1 || (encrypted == -1 && Settings::getInstance().getEncryptLogs())) if (encrypted == 1 || (encrypted == -1 && Nexus::getProfile()->isEncrypted()))
return baseDir.filePath(currentProfile + ".qtox_history.encrypted"); return baseDir.filePath(currentProfile + ".qtox_history.encrypted");
else else
return baseDir.filePath(currentProfile + ".qtox_history"); return baseDir.filePath(currentProfile + ".qtox_history");

30
src/main.cpp

@ -12,6 +12,7 @@
See the COPYING file for more details. See the COPYING file for more details.
*/ */
#include "toxme.h"
#include "widget/widget.h" #include "widget/widget.h"
#include "misc/settings.h" #include "misc/settings.h"
#include "src/nexus.h" #include "src/nexus.h"
@ -19,7 +20,9 @@
#include "src/widget/toxuri.h" #include "src/widget/toxuri.h"
#include "src/widget/toxsave.h" #include "src/widget/toxsave.h"
#include "src/autoupdate.h" #include "src/autoupdate.h"
#include "src/profile.h"
#include "src/profilelocker.h" #include "src/profilelocker.h"
#include "src/widget/loginscreen.h"
#include <QApplication> #include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QDateTime> #include <QDateTime>
@ -31,9 +34,6 @@
#include <QProcess> #include <QProcess>
#include <sodium.h> #include <sodium.h>
#include "toxme.h"
#include <unistd.h> #include <unistd.h>
#define EXIT_UPDATE_MACX 218 //We track our state using unique exit codes when debugging #define EXIT_UPDATE_MACX 218 //We track our state using unique exit codes when debugging
@ -114,15 +114,28 @@ int main(int argc, char *argv[])
if (parser.isSet("p")) if (parser.isSet("p"))
{ {
QString profile = parser.value("p"); QString profileName = parser.value("p");
if (QDir(Settings::getSettingsDirPath()).exists(profile + ".tox")) if (QDir(Settings::getSettingsDirPath()).exists(profileName + ".tox"))
{ {
qDebug() << "Setting profile to" << profile; qDebug() << "Setting profile to" << profileName;
Settings::getInstance().switchProfile(profile); if (Profile::isEncrypted(profileName))
{
Settings::getInstance().setCurrentProfile(profileName);
}
else
{
Profile* profile = Profile::loadProfile(profileName);
if (!profile)
{
qCritical() << "-p profile" << profileName + ".tox" << " couldn't be loaded";
return EXIT_FAILURE;
}
Nexus::getInstance().setProfile(profile);
}
} }
else else
{ {
qCritical() << "-p profile" << profile + ".tox" << "doesn't exist"; qCritical() << "-p profile" << profileName + ".tox" << "doesn't exist";
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} }
@ -292,7 +305,6 @@ int main(int argc, char *argv[])
Nexus::getInstance().start(); Nexus::getInstance().start();
// Run // Run
a.setQuitOnLastWindowClosed(false);
int errorcode = a.exec(); int errorcode = a.exec();
#ifdef LOG_TO_FILE #ifdef LOG_TO_FILE

8
src/misc/db/encrypteddb.cpp

@ -85,7 +85,7 @@ bool EncryptedDb::pullFileContent(const QString &fname, QByteArray &buf)
while (!dbFile.atEnd()) while (!dbFile.atEnd())
{ {
QByteArray encrChunk = dbFile.read(encryptedChunkSize); QByteArray encrChunk = dbFile.read(encryptedChunkSize);
buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory); buf = Core::getInstance()->decryptData(encrChunk);
if (buf.size() > 0) if (buf.size() > 0)
{ {
fileContent += buf; fileContent += buf;
@ -130,7 +130,7 @@ void EncryptedDb::appendToEncrypted(const QString &sql)
{ {
QByteArray filledChunk = buffer.left(plainChunkSize); QByteArray filledChunk = buffer.left(plainChunkSize);
encrFile.seek(chunkPosition * encryptedChunkSize); encrFile.seek(chunkPosition * encryptedChunkSize);
QByteArray encr = Core::getInstance()->encryptData(filledChunk, Core::ptHistory); QByteArray encr = Core::getInstance()->encryptData(filledChunk);
if (encr.size() > 0) if (encr.size() > 0)
{ {
encrFile.write(encr); encrFile.write(encr);
@ -142,7 +142,7 @@ void EncryptedDb::appendToEncrypted(const QString &sql)
} }
encrFile.seek(chunkPosition * encryptedChunkSize); encrFile.seek(chunkPosition * encryptedChunkSize);
QByteArray encr = Core::getInstance()->encryptData(buffer, Core::ptHistory); QByteArray encr = Core::getInstance()->encryptData(buffer);
if (encr.size() > 0) if (encr.size() > 0)
encrFile.write(encr); encrFile.write(encr);
@ -158,7 +158,7 @@ bool EncryptedDb::check(const QString &fname)
if (file.size() > 0) if (file.size() > 0)
{ {
QByteArray encrChunk = file.read(encryptedChunkSize); QByteArray encrChunk = file.read(encryptedChunkSize);
QByteArray buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory); QByteArray buf = Core::getInstance()->decryptData(encrChunk);
if (buf.size() == 0) if (buf.size() == 0)
state = false; state = false;
} }

185
src/misc/settings.cpp

@ -40,8 +40,7 @@
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true #define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
const QString Settings::OLDFILENAME = "settings.ini"; const QString Settings::globalSettingsFile = "qtox.ini";
const QString Settings::FILENAME = "qtox.ini";
Settings* Settings::settings{nullptr}; Settings* Settings::settings{nullptr};
bool Settings::makeToxPortable{false}; bool Settings::makeToxPortable{false};
@ -59,130 +58,17 @@ Settings& Settings::getInstance()
return *settings; return *settings;
} }
void Settings::switchProfile(const QString& profile)
{
// Saves current profile as main profile if this instance is main instance
setCurrentProfile(profile);
save(false);
// If this instance is not main instance previous save did not happen therefore
// we manually set profile again and load profile settings
setCurrentProfile(profile);
loaded = false;
load();
}
QString Settings::genRandomProfileName()
{
QDir dir(getSettingsDirPath());
QString basename = "imported_";
QString randname;
do {
randname = QString().setNum(qrand()*qrand()*qrand(), 16);
randname.truncate(6);
randname = basename + randname;
} while (QFile(dir.filePath(randname)).exists());
return randname;
}
QString Settings::detectProfile()
{
QDir dir(getSettingsDirPath());
QString path, profile = getCurrentProfile();
path = dir.filePath(profile + Core::TOX_EXT);
QFile file(path);
if (profile.isEmpty() || !file.exists())
{
setCurrentProfile("");
#if 1 // deprecation attempt
// if the last profile doesn't exist, fall back to old "data"
path = dir.filePath(Core::CONFIG_FILE_NAME);
QFile file(path);
if (file.exists())
{
profile = genRandomProfileName();
setCurrentProfile(profile);
file.rename(profile + Core::TOX_EXT);
return profile;
}
else if (QFile(path = dir.filePath("tox_save")).exists()) // also import tox_save if no data
{
profile = genRandomProfileName();
setCurrentProfile(profile);
QFile(path).rename(profile + Core::TOX_EXT);
return profile;
}
else
#endif
{
profile = askProfiles();
if (profile.isEmpty())
{
return "";
}
else
{
switchProfile(profile);
return dir.filePath(profile + Core::TOX_EXT);
}
}
}
else
{
return path;
}
}
QList<QString> Settings::searchProfiles()
{
QList<QString> out;
QDir dir(getSettingsDirPath());
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
dir.setNameFilters(QStringList("*.tox"));
for (QFileInfo file : dir.entryInfoList())
out += file.completeBaseName();
return out;
}
QString Settings::askProfiles()
{ // TODO: allow user to create new Tox ID, even if a profile already exists
QList<QString> profiles = searchProfiles();
if (profiles.empty()) return "";
bool ok;
QString profile = GUI::itemInputDialog(nullptr,
tr("Choose a profile"),
tr("Please choose which identity to use"),
profiles,
0, // which slot to start on
false, // if the user can enter their own input
&ok);
if (!ok) // user cancelled
return "";
else
return profile;
}
void Settings::load() void Settings::load()
{ {
if (loaded) if (loaded)
return; return;
createSettingsDir();
QDir dir(getSettingsDirPath()); QDir dir(getSettingsDirPath());
if (!dir.exists())
dir.mkpath(".");
if (QFile(FILENAME).exists()) if (QFile(globalSettingsFile).exists())
{
QSettings ps(FILENAME, QSettings::IniFormat);
ps.beginGroup("General");
makeToxPortable = ps.value("makeToxPortable", false).toBool();
ps.endGroup();
}
else if (QFile(OLDFILENAME).exists())
{ {
QSettings ps(OLDFILENAME, QSettings::IniFormat); QSettings ps(globalSettingsFile, QSettings::IniFormat);
ps.beginGroup("General"); ps.beginGroup("General");
makeToxPortable = ps.value("makeToxPortable", false).toBool(); makeToxPortable = ps.value("makeToxPortable", false).toBool();
ps.endGroup(); ps.endGroup();
@ -192,16 +78,13 @@ void Settings::load()
makeToxPortable = false; makeToxPortable = false;
} }
QString filePath = dir.filePath(FILENAME); QString filePath = dir.filePath(globalSettingsFile);
//if no settings file exist -- use the default one // If no settings file exist -- use the default one
if (!QFile(filePath).exists()) if (!QFile(filePath).exists())
{ {
if (!QFile(filePath = dir.filePath(OLDFILENAME)).exists()) qDebug() << "No settings file found, using defaults";
{ filePath = ":/conf/" + globalSettingsFile;
qDebug() << "No settings file found, using defaults";
filePath = ":/conf/" + FILENAME;
}
} }
qDebug() << "Loading settings from " + filePath; qDebug() << "Loading settings from " + filePath;
@ -366,15 +249,13 @@ void Settings::load()
ps.beginGroup("Privacy"); ps.beginGroup("Privacy");
typingNotification = ps.value("typingNotification", false).toBool(); typingNotification = ps.value("typingNotification", false).toBool();
enableLogging = ps.value("enableLogging", false).toBool(); enableLogging = ps.value("enableLogging", false).toBool();
encryptLogs = ps.value("encryptLogs", false).toBool();
encryptTox = ps.value("encryptTox", false).toBool();
ps.endGroup(); ps.endGroup();
} }
} }
void Settings::save(bool writePersonal) void Settings::save(bool writePersonal)
{ {
QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME); QString filePath = QDir(getSettingsDirPath()).filePath(globalSettingsFile);
save(filePath, writePersonal); save(filePath, writePersonal);
} }
@ -513,8 +394,6 @@ void Settings::savePersonal(QString path)
ps.beginGroup("Privacy"); ps.beginGroup("Privacy");
ps.setValue("typingNotification", typingNotification); ps.setValue("typingNotification", typingNotification);
ps.setValue("enableLogging", enableLogging); ps.setValue("enableLogging", enableLogging);
ps.setValue("encryptLogs", encryptLogs);
ps.setValue("encryptTox", encryptTox);
ps.endGroup(); ps.endGroup();
} }
@ -625,7 +504,7 @@ bool Settings::getMakeToxPortable() const
void Settings::setMakeToxPortable(bool newValue) void Settings::setMakeToxPortable(bool newValue)
{ {
makeToxPortable = newValue; makeToxPortable = newValue;
save(FILENAME); // Commit to the portable file that we don't want to use it save(globalSettingsFile); // Commit to the portable file that we don't want to use it
if (!newValue) // Update the new file right now if not already done if (!newValue) // Update the new file right now if not already done
save(); save();
} }
@ -848,26 +727,6 @@ void Settings::setEnableLogging(bool newValue)
enableLogging = newValue; enableLogging = newValue;
} }
bool Settings::getEncryptLogs() const
{
return encryptLogs;
}
void Settings::setEncryptLogs(bool newValue)
{
encryptLogs = newValue;
}
bool Settings::getEncryptTox() const
{
return encryptTox;
}
void Settings::setEncryptTox(bool newValue)
{
encryptTox = newValue;
}
Db::syncType Settings::getDbSyncType() const Db::syncType Settings::getDbSyncType() const
{ {
return dbSyncType; return dbSyncType;
@ -1246,7 +1105,6 @@ bool Settings::getCompactLayout() const
void Settings::setCompactLayout(bool value) void Settings::setCompactLayout(bool value)
{ {
compactLayout = value; compactLayout = value;
emit compactLayoutChanged();
} }
bool Settings::getGroupchatPosition() const bool Settings::getGroupchatPosition() const
@ -1268,3 +1126,26 @@ void Settings::setThemeColor(const int &value)
{ {
themeColor = value; themeColor = value;
} }
void Settings::createPersonal(QString basename)
{
QString path = getSettingsDirPath() + QDir::separator() + basename + ".ini";
qDebug() << "Creating new profile settings in " << path;
QSettings ps(path, QSettings::IniFormat);
ps.beginGroup("Friends");
ps.beginWriteArray("Friend", 0);
ps.endArray();
ps.endGroup();
ps.beginGroup("Privacy");
ps.endGroup();
}
void Settings::createSettingsDir()
{
QString dir = Settings::getSettingsDirPath();
QDir directory(dir);
if (!directory.exists() && !directory.mkpath(directory.absolutePath()))
qCritical() << "Error while creating directory " << dir;
}

25
src/misc/settings.h

@ -30,14 +30,11 @@ class Settings : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
static Settings& getInstance();
void switchProfile(const QString& profile);
QString detectProfile();
QList<QString> searchProfiles();
QString askProfiles();
~Settings() = default; ~Settings() = default;
static Settings& getInstance();
void createSettingsDir(); ///< Creates a path to the settings dir, if it doesn't already exist
void executeSettingsDialog(QWidget* parent); void createPersonal(QString basename); ///< Write a default personnal settings file for a profile
static QString getSettingsDirPath(); static QString getSettingsDirPath();
@ -107,12 +104,6 @@ public:
bool getEnableLogging() const; bool getEnableLogging() const;
void setEnableLogging(bool newValue); void setEnableLogging(bool newValue);
bool getEncryptLogs() const;
void setEncryptLogs(bool newValue);
bool getEncryptTox() const;
void setEncryptTox(bool newValue);
Db::syncType getDbSyncType() const; Db::syncType getDbSyncType() const;
void setDbSyncType(int newValue); void setDbSyncType(int newValue);
@ -259,9 +250,6 @@ public:
void save(QString path, bool writePersonal = true); void save(QString path, bool writePersonal = true);
void load(); void load();
private:
static QString genRandomProfileName();
private: private:
static Settings* settings; static Settings* settings;
@ -272,7 +260,7 @@ private:
void saveGlobal(QString path); void saveGlobal(QString path);
void savePersonal(QString path); void savePersonal(QString path);
static const QString FILENAME; static const QString globalSettingsFile;
static const QString OLDFILENAME; static const QString OLDFILENAME;
bool loaded; bool loaded;
@ -309,8 +297,6 @@ private:
uint32_t currentProfileId; uint32_t currentProfileId;
bool enableLogging; bool enableLogging;
bool encryptLogs;
bool encryptTox = false;
int autoAwayTime; int autoAwayTime;
@ -365,12 +351,9 @@ private:
int themeColor; int themeColor;
signals: signals:
//void dataChanged();
void dhtServerListChanged(); void dhtServerListChanged();
void logStorageOptsChanged();
void smileyPackChanged(); void smileyPackChanged();
void emojiFontChanged(); void emojiFontChanged();
void compactLayoutChanged();
}; };
#endif // SETTINGS_HPP #endif // SETTINGS_HPP

87
src/nexus.cpp

@ -1,47 +1,50 @@
#include "nexus.h" #include "nexus.h"
#include "profile.h"
#include "src/core/core.h" #include "src/core/core.h"
#include "misc/settings.h" #include "misc/settings.h"
#include "video/camerasource.h" #include "video/camerasource.h"
#include "widget/gui.h" #include "widget/gui.h"
#include "widget/loginscreen.h"
#include <QThread> #include <QThread>
#include <QDebug> #include <QDebug>
#include <QImageReader> #include <QImageReader>
#include <QFile> #include <QFile>
#include <QApplication>
#include <cassert>
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include <src/widget/androidgui.h> #include <src/widget/androidgui.h>
#else #else
#include <src/widget/widget.h> #include <src/widget/widget.h>
#include <QDesktopWidget>
#endif #endif
static Nexus* nexus{nullptr}; static Nexus* nexus{nullptr};
Nexus::Nexus(QObject *parent) : Nexus::Nexus(QObject *parent) :
QObject(parent), QObject(parent),
core{nullptr}, profile{nullptr},
coreThread{nullptr},
widget{nullptr}, widget{nullptr},
androidgui{nullptr}, androidgui{nullptr},
started{false} loginScreen{nullptr}
{ {
} }
Nexus::~Nexus() Nexus::~Nexus()
{ {
delete core;
delete coreThread;
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
delete androidgui; delete androidgui;
#else #else
delete widget; delete widget;
#endif #endif
delete loginScreen;
delete profile;
if (profile)
Settings::getInstance().save();
} }
void Nexus::start() void Nexus::start()
{ {
if (started)
return;
qDebug() << "Starting up"; qDebug() << "Starting up";
// Setup the environment // Setup the environment
@ -56,22 +59,49 @@ void Nexus::start()
qRegisterMetaType<QPixmap>("QPixmap"); qRegisterMetaType<QPixmap>("QPixmap");
qRegisterMetaType<ToxFile>("ToxFile"); qRegisterMetaType<ToxFile>("ToxFile");
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection"); qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
qRegisterMetaType<Core::PasswordType>("Core::PasswordType");
qRegisterMetaType<std::shared_ptr<VideoFrame>>("std::shared_ptr<VideoFrame>"); qRegisterMetaType<std::shared_ptr<VideoFrame>>("std::shared_ptr<VideoFrame>");
loginScreen = new LoginScreen();
if (profile)
showMainGUI();
else
showLogin();
}
void Nexus::showLogin()
{
#ifdef Q_OS_ANDROID
delete androidui;
androidgui = nullptr;
#else
delete widget;
widget = nullptr;
#endif
delete profile;
profile = nullptr;
loginScreen->reset();
#ifndef Q_OS_ANDROID
loginScreen->move(QApplication::desktop()->screen()->rect().center() - loginScreen->rect().center());
#endif
loginScreen->show();
((QApplication*)qApp)->setQuitOnLastWindowClosed(true);
}
void Nexus::showMainGUI()
{
assert(profile);
((QApplication*)qApp)->setQuitOnLastWindowClosed(false);
loginScreen->close();
// Create GUI // Create GUI
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
widget = Widget::getInstance(); widget = Widget::getInstance();
#endif #endif
// Create Core
QString profilePath = Settings::getInstance().detectProfile();
coreThread = new QThread(this);
coreThread->setObjectName("qTox Core");
core = new Core(coreThread, profilePath);
core->moveToThread(coreThread);
connect(coreThread, &QThread::started, core, &Core::start);
// Start GUI // Start GUI
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
androidgui = new AndroidGUI; androidgui = new AndroidGUI;
@ -88,6 +118,7 @@ void Nexus::start()
GUI::setEnabled(false); GUI::setEnabled(false);
// Connections // Connections
Core* core = profile->getCore();
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
connect(core, &Core::connected, androidgui, &AndroidGUI::onConnected); connect(core, &Core::connected, androidgui, &AndroidGUI::onConnected);
connect(core, &Core::disconnected, androidgui, &AndroidGUI::onDisconnected); connect(core, &Core::disconnected, androidgui, &AndroidGUI::onDisconnected);
@ -126,7 +157,6 @@ void Nexus::start()
connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying); connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying);
connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated); connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated);
connect(core, &Core::avInvite, widget, &Widget::playRingtone); connect(core, &Core::avInvite, widget, &Widget::playRingtone);
connect(core, &Core::blockingClearContacts, widget, &Widget::clearContactsList, Qt::BlockingQueuedConnection);
connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged); connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged);
connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult); connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult);
@ -135,13 +165,9 @@ void Nexus::start()
connect(widget, &Widget::statusSet, core, &Core::setStatus); connect(widget, &Widget::statusSet, core, &Core::setStatus);
connect(widget, &Widget::friendRequested, core, &Core::requestFriendship); connect(widget, &Widget::friendRequested, core, &Core::requestFriendship);
connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest); connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest);
connect(widget, &Widget::changeProfile, core, &Core::switchConfiguration);
#endif #endif
// Start Core profile->startCore();
coreThread->start();
started = true;
} }
Nexus& Nexus::getInstance() Nexus& Nexus::getInstance()
@ -160,7 +186,20 @@ void Nexus::destroyInstance()
Core* Nexus::getCore() Core* Nexus::getCore()
{ {
return getInstance().core; Nexus& nexus = getInstance();
if (!nexus.profile)
return nullptr;
return nexus.profile->getCore();
}
Profile* Nexus::getProfile()
{
return getInstance().profile;
}
void Nexus::setProfile(Profile* profile)
{
getInstance().profile = profile;
} }
AndroidGUI* Nexus::getAndroidGUI() AndroidGUI* Nexus::getAndroidGUI()

18
src/nexus.h

@ -3,10 +3,11 @@
#include <QObject> #include <QObject>
class QThread;
class Core;
class Widget; class Widget;
class AndroidGUI; class AndroidGUI;
class Profile;
class LoginScreen;
class Core;
/// This class is in charge of connecting various systems together /// This class is in charge of connecting various systems together
/// and forwarding signals appropriately to the right objects /// and forwarding signals appropriately to the right objects
@ -15,11 +16,17 @@ class Nexus : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
void start(); ///< Will initialise the systems (GUI, Core, ...) void start(); ///< Sets up invariants and calls showLogin
void showLogin(); ///< Hides the man GUI, delete the profile, and shows the login screen
/// Hides the login screen and shows the GUI for the given profile.
/// Will delete the current GUI, if it exists.
void showMainGUI();
static Nexus& getInstance(); static Nexus& getInstance();
static void destroyInstance(); static void destroyInstance();
static Core* getCore(); ///< Will return 0 if not started 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 AndroidGUI* getAndroidGUI(); ///< Will return 0 if not started static AndroidGUI* getAndroidGUI(); ///< Will return 0 if not started
static Widget* getDesktopGUI(); ///< Will return 0 if not started static Widget* getDesktopGUI(); ///< Will return 0 if not started
static QString getSupportedImageFilter(); static QString getSupportedImageFilter();
@ -30,11 +37,10 @@ private:
~Nexus(); ~Nexus();
private: private:
Core* core; Profile* profile;
QThread* coreThread;
Widget* widget; Widget* widget;
AndroidGUI* androidgui; AndroidGUI* androidgui;
bool started; LoginScreen* loginScreen;
}; };
#endif // NEXUS_H #endif // NEXUS_H

339
src/profile.cpp

@ -0,0 +1,339 @@
#include "profile.h"
#include "profilelocker.h"
#include "src/misc/settings.h"
#include "src/core/core.h"
#include "src/historykeeper.h"
#include "src/widget/gui.h"
#include "src/widget/widget.h"
#include "src/nexus.h"
#include <cassert>
#include <QDir>
#include <QFileInfo>
#include <QSaveFile>
#include <QThread>
#include <QObject>
#include <QDebug>
QVector<QString> Profile::profiles;
Profile::Profile(QString name, QString password, bool isNewProfile)
: name{name}, password{password},
newProfile{isNewProfile}, isRemoved{false}
{
Settings::getInstance().setCurrentProfile(name);
HistoryKeeper::resetInstance();
coreThread = new QThread();
coreThread->setObjectName("qTox Core");
core = new Core(coreThread, *this);
core->moveToThread(coreThread);
QObject::connect(coreThread, &QThread::started, core, &Core::start);
}
Profile* Profile::loadProfile(QString name, QString password)
{
if (ProfileLocker::hasLock())
{
qCritical() << "Tried to load profile "<<name<<", but another profile is already locked!";
return nullptr;
}
if (!ProfileLocker::lock(name))
{
qWarning() << "Failed to lock profile "<<name;
return nullptr;
}
return new Profile(name, password, false);
}
Profile* Profile::createProfile(QString name, QString password)
{
if (ProfileLocker::hasLock())
{
qCritical() << "Tried to create profile "<<name<<", but another profile is already locked!";
return nullptr;
}
if (profileExists(name))
{
qCritical() << "Tried to create profile "<<name<<", but it already exists!";
return nullptr;
}
if (!ProfileLocker::lock(name))
{
qWarning() << "Failed to lock profile "<<name;
return nullptr;
}
Settings::getInstance().createPersonal(name);
return new Profile(name, password, true);
}
Profile::~Profile()
{
if (!isRemoved && core->isReady())
saveToxSave();
delete core;
delete coreThread;
ProfileLocker::assertLock();
assert(ProfileLocker::getCurLockName() == name);
ProfileLocker::unlock();
}
QVector<QString> Profile::getFilesByExt(QString extension)
{
QDir dir(Settings::getInstance().getSettingsDirPath());
QVector<QString> out;
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
dir.setNameFilters(QStringList("*."+extension));
QFileInfoList list = dir.entryInfoList();
out.reserve(list.size());
for (QFileInfo file : list)
out += file.completeBaseName();
return out;
}
void Profile::scanProfiles()
{
profiles.clear();
QVector<QString> toxfiles = getFilesByExt("tox"), inifiles = getFilesByExt("ini");
for (QString toxfile : toxfiles)
{
if (!inifiles.contains(toxfile))
importProfile(toxfile);
profiles.append(toxfile);
}
}
void Profile::importProfile(QString name)
{
assert(!profileExists(name));
Settings::getInstance().createPersonal(name);
}
QVector<QString> Profile::getProfiles()
{
return profiles;
}
Core* Profile::getCore()
{
return core;
}
QString Profile::getName()
{
return name;
}
void Profile::startCore()
{
coreThread->start();
}
bool Profile::isNewProfile()
{
return newProfile;
}
QByteArray Profile::loadToxSave()
{
assert(!isRemoved);
/// TODO: Cache the data, invalidate it only when we save
QByteArray data;
QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox";
QFile saveFile(path);
qint64 fileSize;
qDebug() << "Loading tox save "<<path;
if (!saveFile.exists())
{
qWarning() << "The tox save file "<<path<<" was not found";
goto fail;
}
if (!saveFile.open(QIODevice::ReadOnly))
{
qCritical() << "The tox save file " << path << " couldn't' be opened";
goto fail;
}
fileSize = saveFile.size();
if (fileSize <= 0)
{
qWarning() << "The tox save file"<<path<<" is empty!";
goto fail;
}
data = saveFile.readAll();
if (tox_is_data_encrypted((uint8_t*)data.data()))
{
if (password.isEmpty())
{
qCritical() << "The tox save file is encrypted, but we don't have a password!";
data.clear();
goto fail;
}
uint8_t salt[TOX_PASS_SALT_LENGTH];
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
core->setPassword(password, salt);
data = core->decryptData(data);
if (data.isEmpty())
qCritical() << "Failed to decrypt the tox save file";
}
else
{
if (!password.isEmpty())
qWarning() << "We have a password, but the tox save file is not encrypted";
}
fail:
saveFile.close();
return data;
}
void Profile::saveToxSave()
{
assert(core->isReady());
QByteArray data = core->getToxSaveData();
assert(data.size());
saveToxSave(data);
}
void Profile::saveToxSave(QByteArray data)
{
assert(!isRemoved);
ProfileLocker::assertLock();
assert(ProfileLocker::getCurLockName() == name);
QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox";
qDebug() << "Saving tox save to "<<path;
QSaveFile saveFile(path);
if (!saveFile.open(QIODevice::WriteOnly))
{
qCritical() << "Tox save file " << path << " couldn't be opened";
return;
}
if (!password.isEmpty())
{
core->setPassword(password);
data = core->encryptData(data);
if (data.isEmpty())
{
qCritical() << "Failed to encrypt, can't save!";
saveFile.cancelWriting();
return;
}
}
saveFile.write(data);
saveFile.commit();
newProfile = false;
}
bool Profile::profileExists(QString name)
{
QString path = Settings::getSettingsDirPath() + QDir::separator() + name;
return QFile::exists(path+".tox") && QFile::exists(path+".ini");
}
bool Profile::isEncrypted()
{
return !password.isEmpty();
}
bool Profile::isEncrypted(QString name)
{
uint8_t data[encryptHeaderSize] = {0};
QString path = Settings::getSettingsDirPath() + QDir::separator() + name + ".tox";
QFile saveFile(path);
if (!saveFile.open(QIODevice::ReadOnly))
{
qWarning() << "Couldn't open tox save "<<path;
return false;
}
saveFile.read((char*)data, encryptHeaderSize);
saveFile.close();
return tox_is_data_encrypted(data);
}
void Profile::remove()
{
if (isRemoved)
{
qWarning() << "Profile "<<name<<" is already removed!";
return;
}
isRemoved = true;
qDebug() << "Removing profile"<<name;
profiles.removeAll(name);
QString path = Settings::getSettingsDirPath() + QDir::separator() + name;
QFile::remove(path+".tox");
QFile::remove(path+".ini");
QFile::remove(HistoryKeeper::getHistoryPath(name, 0));
QFile::remove(HistoryKeeper::getHistoryPath(name, 1));
}
bool Profile::rename(QString newName)
{
QString path = Settings::getSettingsDirPath() + QDir::separator() + name,
newPath = Settings::getSettingsDirPath() + QDir::separator() + newName;
if (!ProfileLocker::lock(newName))
return false;
QFile::rename(path+".tox", newPath+".tox");
QFile::rename(path+".ini", newPath+".ini");
HistoryKeeper::renameHistory(name, newName);
bool resetAutorun = Settings::getInstance().getAutorun();
Settings::getInstance().setAutorun(false);
Settings::getInstance().setCurrentProfile(newName);
if (resetAutorun)
Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line
name = newName;
return true;
}
bool Profile::checkPassword()
{
if (isRemoved)
return false;
return !loadToxSave().isEmpty();
}
QString Profile::getPassword()
{
return password;
}
void Profile::restartCore()
{
GUI::setEnabled(false); // Core::reset re-enables it
if (!isRemoved && core->isReady())
saveToxSave();
QMetaObject::invokeMethod(core, "reset");
}
void Profile::setPassword(QString newPassword)
{
QList<HistoryKeeper::HistMessage> oldMessages = HistoryKeeper::exportMessagesDeleteFile();
password = newPassword;
core->setPassword(password);
saveToxSave();
HistoryKeeper::getInstance()->importMessages(oldMessages);
Nexus::getDesktopGUI()->reloadHistory();
}

76
src/profile.h

@ -0,0 +1,76 @@
#ifndef PROFILE_H
#define PROFILE_H
#include <QVector>
#include <QString>
#include <QByteArray>
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, 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();
void startCore(); ///< Starts the Core thread
void restartCore(); ///< Delete core and restart a new one
bool isNewProfile();
bool isEncrypted(); ///< Returns true if we have a password set (doesn't check the actual file on disk)
bool checkPassword(); ///< Checks whether the password is valid
QString getPassword();
void setPassword(QString newPassword); ///< Changes the encryption password and re-saves everything with it
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.
/// Removes the profile permanently
/// It is invalid to call loadToxSave or saveToxSave on a deleted profile
/// Updates the profiles vector
void 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 profileExists(QString name);
static bool isEncrypted(QString name); ///< Returns false on error. Checks the actual file on disk.
private:
Profile(QString name, 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);
private:
Core* core;
QThread* coreThread;
QString name, password;
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()
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;
};
#endif // PROFILE_H

13
src/profilelocker.cpp

@ -97,3 +97,16 @@ void ProfileLocker::deathByBrokenLock()
qCritical() << "Lock is *BROKEN*, exiting immediately"; qCritical() << "Lock is *BROKEN*, exiting immediately";
abort(); abort();
} }
bool ProfileLocker::hasLock()
{
return lockfile.operator bool();
}
QString ProfileLocker::getCurLockName()
{
if (lockfile)
return curLockName;
else
return QString();
}

4
src/profilelocker.h

@ -24,6 +24,10 @@ public:
static bool lock(QString profile); static bool lock(QString profile);
/// Releases the lock on the current profile /// Releases the lock on the current profile
static void unlock(); 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();
/// Releases all locks on all profiles /// Releases all locks on all profiles
/// DO NOT call unless all we're the only qTox instance /// DO NOT call unless all we're the only qTox instance
/// and we don't hold any lock yet. /// and we don't hold any lock yet.

197
src/widget/form/profileform.cpp

@ -19,6 +19,7 @@
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
#include "src/widget/form/settingswidget.h" #include "src/widget/form/settingswidget.h"
#include "src/widget/maskablepixmapwidget.h" #include "src/widget/maskablepixmapwidget.h"
#include "src/widget/form/setpassworddialog.h"
#include "src/misc/settings.h" #include "src/misc/settings.h"
#include "src/widget/croppinglabel.h" #include "src/widget/croppinglabel.h"
#include "src/widget/widget.h" #include "src/widget/widget.h"
@ -26,6 +27,7 @@
#include "src/historykeeper.h" #include "src/historykeeper.h"
#include "src/misc/style.h" #include "src/misc/style.h"
#include "src/profilelocker.h" #include "src/profilelocker.h"
#include "src/profile.h"
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QGroupBox> #include <QGroupBox>
@ -35,17 +37,6 @@
#include <QFileDialog> #include <QFileDialog>
#include <QBuffer> #include <QBuffer>
void ProfileForm::refreshProfiles()
{
bodyUI->profiles->clear();
for (QString profile : Settings::getInstance().searchProfiles())
bodyUI->profiles->addItem(profile);
QString current = Settings::getInstance().getCurrentProfile();
if (current != "")
bodyUI->profiles->setCurrentText(current);
}
ProfileForm::ProfileForm(QWidget *parent) : ProfileForm::ProfileForm(QWidget *parent) :
QWidget{parent}, qr{nullptr} QWidget{parent}, qr{nullptr}
{ {
@ -98,25 +89,14 @@ ProfileForm::ProfileForm(QWidget *parent) :
connect(bodyUI->toxIdLabel, SIGNAL(clicked()), this, SLOT(copyIdClicked())); connect(bodyUI->toxIdLabel, SIGNAL(clicked()), this, SLOT(copyIdClicked()));
connect(toxId, SIGNAL(clicked()), this, SLOT(copyIdClicked())); connect(toxId, SIGNAL(clicked()), this, SLOT(copyIdClicked()));
connect(core, &Core::idSet, this, &ProfileForm::setToxId); connect(core, &Core::idSet, this, &ProfileForm::setToxId);
connect(core, &Core::statusSet, this, &ProfileForm::onStatusSet);
connect(bodyUI->userName, SIGNAL(editingFinished()), this, SLOT(onUserNameEdited())); connect(bodyUI->userName, SIGNAL(editingFinished()), this, SLOT(onUserNameEdited()));
connect(bodyUI->statusMessage, SIGNAL(editingFinished()), this, SLOT(onStatusMessageEdited())); connect(bodyUI->statusMessage, SIGNAL(editingFinished()), this, SLOT(onStatusMessageEdited()));
connect(bodyUI->loadButton, &QPushButton::clicked, this, &ProfileForm::onLoadClicked);
connect(bodyUI->renameButton, &QPushButton::clicked, this, &ProfileForm::onRenameClicked); connect(bodyUI->renameButton, &QPushButton::clicked, this, &ProfileForm::onRenameClicked);
connect(bodyUI->exportButton, &QPushButton::clicked, this, &ProfileForm::onExportClicked); connect(bodyUI->exportButton, &QPushButton::clicked, this, &ProfileForm::onExportClicked);
connect(bodyUI->deleteButton, &QPushButton::clicked, this, &ProfileForm::onDeleteClicked); connect(bodyUI->deleteButton, &QPushButton::clicked, this, &ProfileForm::onDeleteClicked);
connect(bodyUI->importButton, &QPushButton::clicked, this, &ProfileForm::onImportClicked); connect(bodyUI->logoutButton, &QPushButton::clicked, this, &ProfileForm::onLogoutClicked);
connect(bodyUI->newButton, &QPushButton::clicked, this, &ProfileForm::onNewClicked); connect(bodyUI->deletePassButton, &QPushButton::clicked, this, &ProfileForm::onDeletePassClicked);
connect(bodyUI->changePassButton, &QPushButton::clicked, this, &ProfileForm::onChangePassClicked);
connect(core, &Core::avStart, this, &ProfileForm::disableSwitching);
connect(core, &Core::avStarting, this, &ProfileForm::disableSwitching);
connect(core, &Core::avInvite, this, &ProfileForm::disableSwitching);
connect(core, &Core::avRinging, this, &ProfileForm::disableSwitching);
connect(core, &Core::avCancel, this, &ProfileForm::enableSwitching);
connect(core, &Core::avEnd, this, &ProfileForm::enableSwitching);
connect(core, &Core::avEnding, this, &ProfileForm::enableSwitching);
connect(core, &Core::avPeerTimeout, this, &ProfileForm::enableSwitching);
connect(core, &Core::avRequestTimeout, this, &ProfileForm::enableSwitching);
connect(core, &Core::usernameSet, this, [=](const QString& val) { bodyUI->userName->setText(val); }); connect(core, &Core::usernameSet, this, [=](const QString& val) { bodyUI->userName->setText(val); });
connect(core, &Core::statusMessageSet, this, [=](const QString& val) { bodyUI->statusMessage->setText(val); }); connect(core, &Core::statusMessageSet, this, [=](const QString& val) { bodyUI->statusMessage->setText(val); });
@ -187,7 +167,6 @@ void ProfileForm::setToxId(const QString& id)
qr = new QRWidget(); qr = new QRWidget();
qr->setQRData("tox:"+id); qr->setQRData("tox:"+id);
bodyUI->qrCode->setPixmap(QPixmap::fromImage(qr->getImage()->scaledToWidth(150))); bodyUI->qrCode->setPixmap(QPixmap::fromImage(qr->getImage()->scaledToWidth(150)));
refreshProfiles();
} }
void ProfileForm::onAvatarClicked() void ProfileForm::onAvatarClicked()
@ -250,58 +229,31 @@ void ProfileForm::onAvatarClicked()
Nexus::getCore()->setAvatar(bytes); Nexus::getCore()->setAvatar(bytes);
} }
void ProfileForm::onLoadClicked()
{
if (bodyUI->profiles->currentText() != Settings::getInstance().getCurrentProfile())
{
if (Core::getInstance()->anyActiveCalls())
GUI::showWarning(tr("Call active", "popup title"),
tr("You can't switch profiles while a call is active!", "popup text"));
else
emit Widget::getInstance()->changeProfile(bodyUI->profiles->currentText());
// I think by directly calling the function, I may have been causing thread issues
}
}
void ProfileForm::onRenameClicked() void ProfileForm::onRenameClicked()
{ {
QString cur = bodyUI->profiles->currentText(); Nexus& nexus = Nexus::getInstance();
QString cur = nexus.getProfile()->getName();
QString title = tr("Rename \"%1\"", "renaming a profile").arg(cur); QString title = tr("Rename \"%1\"", "renaming a profile").arg(cur);
do do
{ {
QString name = QInputDialog::getText(this, title, title+":"); QString name = QInputDialog::getText(this, title, title+":");
if (name.isEmpty()) break; if (name.isEmpty()) break;
name = Core::sanitize(name); name = Core::sanitize(name);
QDir dir(Settings::getSettingsDirPath());
QString file = dir.filePath(name+Core::TOX_EXT);
if (!QFile::exists(file) || GUI::askQuestion(tr("Profile already exists", "rename confirm title"),
tr("A profile named \"%1\" already exists. Do you want to erase it?", "rename confirm text").arg(cur)))
{
if (!ProfileLocker::lock(name))
{
GUI::showWarning(tr("Profile already exists", "rename failed title"),
tr("A profile named \"%1\" already exists and is in use.").arg(cur));
break;
}
QFile::rename(dir.filePath(cur+Core::TOX_EXT), file);
QFile::rename(dir.filePath(cur+".ini"), dir.filePath(name+".ini"));
bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name);
HistoryKeeper::renameHistory(cur, name);
bool resetAutorun = Settings::getInstance().getAutorun();
Settings::getInstance().setAutorun(false);
Settings::getInstance().setCurrentProfile(name);
if (resetAutorun)
Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line
if (Profile::profileExists(name))
GUI::showError(tr("Profile already exists", "rename failure title"),
tr("A profile named \"%1\" already exists.", "rename confirm text").arg(name));
else if (!nexus.getProfile()->rename(name))
GUI::showError(tr("Failed to rename", "rename failed title"),
tr("Couldn't rename the profile to \"%1\"").arg(cur));
else
break; break;
}
} while (true); } while (true);
} }
void ProfileForm::onExportClicked() void ProfileForm::onExportClicked()
{ {
QString current = bodyUI->profiles->currentText() + Core::TOX_EXT; QString current = Nexus::getProfile()->getName() + Core::TOX_EXT;
QString path = QFileDialog::getSaveFileName(0, tr("Export profile", "save dialog title"), QString path = QFileDialog::getSaveFileName(0, tr("Export profile", "save dialog title"),
QDir::home().filePath(current), QDir::home().filePath(current),
tr("Tox save file (*.tox)", "save dialog filter")); tr("Tox save file (*.tox)", "save dialog filter"));
@ -319,97 +271,30 @@ void ProfileForm::onExportClicked()
void ProfileForm::onDeleteClicked() void ProfileForm::onDeleteClicked()
{ {
if (Settings::getInstance().getCurrentProfile() == bodyUI->profiles->currentText()) if (GUI::askQuestion(tr("Really delete profile?","deletion confirmation title"),
{ tr("Are you sure you want to delete this profile?","deletion confirmation text")))
GUI::showWarning(tr("Profile currently loaded","current profile deletion warning title"), tr("This profile is currently in use. Please load a different profile before deleting this one.","current profile deletion warning text"));
}
else
{ {
if (GUI::askQuestion(tr("Deletion imminent!","deletion confirmation title"), Nexus& nexus = Nexus::getInstance();
tr("Are you sure you want to delete this profile?","deletion confirmation text"))) nexus.getProfile()->remove();
{ nexus.showLogin();
QString profile = bodyUI->profiles->currentText();
QDir dir(Settings::getSettingsDirPath());
QFile::remove(dir.filePath(profile + Core::TOX_EXT));
QFile::remove(dir.filePath(profile + ".ini"));
QFile::remove(HistoryKeeper::getHistoryPath(profile, 0));
QFile::remove(HistoryKeeper::getHistoryPath(profile, 1));
bodyUI->profiles->removeItem(bodyUI->profiles->currentIndex());
bodyUI->profiles->setCurrentText(Settings::getInstance().getCurrentProfile());
}
} }
} }
void ProfileForm::onImportClicked() void ProfileForm::onLogoutClicked()
{ {
QString path = QFileDialog::getOpenFileName(0, Nexus& nexus = Nexus::getInstance();
tr("Import profile", "import dialog title"), Settings::getInstance().save();
QDir::homePath(), nexus.showLogin();
tr("Tox save file (*.tox)", "import dialog filter"));
if (path.isEmpty())
return;
QFileInfo info(path);
QString profile = info.completeBaseName();
if (info.suffix() != "tox")
{
GUI::showWarning(tr("Ignoring non-Tox file", "popup title"),
tr("Warning: you've chosen a file that is not a Tox save file; ignoring.", "popup text"));
return;
}
QString profilePath = QDir(Settings::getSettingsDirPath()).filePath(profile + Core::TOX_EXT);
if (QFileInfo(profilePath).exists() && !GUI::askQuestion(tr("Profile already exists", "import confirm title"),
tr("A profile named \"%1\" already exists. Do you want to erase it?", "import confirm text").arg(profile)))
return;
QFile::copy(path, profilePath);
bodyUI->profiles->addItem(profile);
}
void ProfileForm::onStatusSet(Status)
{
refreshProfiles();
}
void ProfileForm::onNewClicked()
{
emit Widget::getInstance()->changeProfile(QString());
} }
void ProfileForm::disableSwitching() void ProfileForm::onCopyQrClicked()
{
bodyUI->loadButton->setEnabled(false);
bodyUI->newButton->setEnabled(false);
}
void ProfileForm::enableSwitching()
{
if (!core->anyActiveCalls())
{
bodyUI->loadButton->setEnabled(true);
bodyUI->newButton->setEnabled(true);
}
}
void ProfileForm::showEvent(QShowEvent *event)
{
refreshProfiles();
QWidget::showEvent(event);
}
void ProfileForm::on_copyQr_clicked()
{ {
QApplication::clipboard()->setImage(*qr->getImage()); QApplication::clipboard()->setImage(*qr->getImage());
} }
void ProfileForm::on_saveQr_clicked() void ProfileForm::onSaveQrClicked()
{ {
QString current = bodyUI->profiles->currentText() + ".png"; QString current = Nexus::getProfile()->getName() + ".png";
QString path = QFileDialog::getSaveFileName(0, tr("Save", "save qr image"), QString path = QFileDialog::getSaveFileName(0, tr("Save", "save qr image"),
QDir::home().filePath(current), QDir::home().filePath(current),
tr("Save QrCode (*.png)", "save dialog filter")); tr("Save QrCode (*.png)", "save dialog filter"));
@ -425,13 +310,29 @@ void ProfileForm::on_saveQr_clicked()
} }
} }
bool ProfileForm::eventFilter(QObject *o, QEvent *e) void ProfileForm::onDeletePassClicked()
{ {
if ((e->type() == QEvent::Wheel) && Profile* pro = Nexus::getProfile();
(qobject_cast<QComboBox*>(o) || qobject_cast<QAbstractSpinBox*>(o) )) if (!pro->isEncrypted())
{ {
e->ignore(); GUI::showInfo(tr("Nothing to remove"), tr("Your profile does not have a password!"));
return true; return;
} }
return QWidget::eventFilter(o, e);
if (!GUI::askQuestion(tr("Really delete password?","deletion confirmation title"),
tr("Are you sure you want to delete your password?","deletion confirmation text")))
return;
Nexus::getProfile()->setPassword(QString());
}
void ProfileForm::onChangePassClicked()
{
SetPasswordDialog* dialog = new SetPasswordDialog(tr("Please enter a new password."), QString(), 0);
int r = dialog->exec();
if (r == QDialog::Rejected)
return;
QString newPass = dialog->getPassword();
Nexus::getProfile()->setPassword(newPass);
} }

18
src/widget/form/profileform.h

@ -56,7 +56,6 @@ signals:
public slots: public slots:
void onSelfAvatarLoaded(const QPixmap &pic); void onSelfAvatarLoaded(const QPixmap &pic);
void onStatusSet(Status status);
private slots: private slots:
void setToxId(const QString& id); void setToxId(const QString& id);
@ -64,21 +63,14 @@ private slots:
void onAvatarClicked(); void onAvatarClicked();
void onUserNameEdited(); void onUserNameEdited();
void onStatusMessageEdited(); void onStatusMessageEdited();
void onLoadClicked();
void onRenameClicked(); void onRenameClicked();
void onExportClicked(); void onExportClicked();
void onDeleteClicked(); void onDeleteClicked();
void onImportClicked(); void onLogoutClicked();
void onNewClicked(); void onCopyQrClicked();
void disableSwitching(); void onSaveQrClicked();
void enableSwitching(); void onDeletePassClicked();
void on_copyQr_clicked(); void onChangePassClicked();
void on_saveQr_clicked();
protected:
virtual void showEvent(QShowEvent *);
bool eventFilter(QObject *o, QEvent *e);
private: private:
void refreshProfiles(); void refreshProfiles();

108
src/widget/form/profileform.ui

@ -39,8 +39,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>565</width> <width>630</width>
<height>617</height> <height>625</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0,1"> <layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0,1">
@ -160,52 +160,41 @@ Share it with your friends to communicate.</string>
<item alignment="Qt::AlignTop"> <item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="profilesGroup"> <widget class="QGroupBox" name="profilesGroup">
<property name="title"> <property name="title">
<string>Profiles</string> <string>Profile</string>
</property> </property>
<layout class="QVBoxLayout" name="profilesVLayout"> <layout class="QVBoxLayout" name="profilesVLayout">
<item> <item>
<layout class="QHBoxLayout" name="profilesComboLayout"> <layout class="QHBoxLayout" name="profilesButtonsLayout">
<item>
<widget class="QLabel" name="profilesLabel">
<property name="text">
<string>Available profiles:</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QComboBox" name="profiles"> <spacer name="horizontalSpacer">
<property name="sizePolicy"> <property name="orientation">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <enum>Qt::Horizontal</enum>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="toolTip"> <property name="sizeHint" stdset="0">
<string comment="toolTip for currently set profile">Currently selected profile.</string> <size>
<width>40</width>
<height>20</height>
</size>
</property> </property>
</widget> </spacer>
</item> </item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="profilesButtonsLayout">
<item> <item>
<widget class="QPushButton" name="loadButton"> <widget class="QPushButton" name="renameButton">
<property name="toolTip"> <property name="toolTip">
<string comment="tooltip for loading profile button">Load selected profile and switch to it.</string> <string comment="tooltip for renaming profile button">Rename profile.</string>
</property> </property>
<property name="text"> <property name="text">
<string comment="load profile button">Load</string> <string comment="rename profile button">Rename</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="renameButton"> <widget class="QPushButton" name="deleteButton">
<property name="toolTip"> <property name="toolTip">
<string comment="tooltip for renaming profile button">Rename selected profile.</string> <string comment="delete profile button tooltip">Delete profile.</string>
</property> </property>
<property name="text"> <property name="text">
<string comment="rename profile button">Rename</string> <string comment="delete profile button">Delete</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -221,39 +210,72 @@ Profile does not contain your history.</string>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="deleteButton"> <widget class="QPushButton" name="logoutButton">
<property name="toolTip"> <property name="toolTip">
<string comment="delete profile button tooltip">Delete selected profile.</string> <string comment="tooltip for logout button">Go back to the login screen</string>
</property> </property>
<property name="text"> <property name="text">
<string comment="delete profile button">Delete</string> <string comment="import profile button">Logout</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="profilesButtonsLayout2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<widget class="QPushButton" name="importButton"> <spacer name="horizontalSpacer_3">
<property name="toolTip"> <property name="orientation">
<string comment="tooltip for importing profile button">Import Tox profile from a .tox file.</string> <enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property> </property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="deletePassButton">
<property name="text"> <property name="text">
<string comment="import profile button">Import a profile</string> <string>Remove password</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="newButton"> <widget class="QPushButton" name="changePassButton">
<property name="toolTip">
<string comment="tooltip for creating new Tox ID button">Create new Tox ID and switch to it.</string>
</property>
<property name="text"> <property name="text">
<string comment="new profile button">New Tox ID</string> <string>Change password</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

69
src/widget/form/setpassworddialog.cpp

@ -48,51 +48,52 @@ void SetPasswordDialog::onPasswordEdit()
{ {
QString pswd = ui->passwordlineEdit->text(); QString pswd = ui->passwordlineEdit->text();
if (pswd == ui->repasswordlineEdit->text() && pswd.length() > 0)
if (pswd.length() < 6)
{ {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
ui->body->setText(body); ui->body->setText(body + tr("The password is too short"));
} }
else else if (pswd != ui->repasswordlineEdit->text())
{ {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
ui->body->setText(body + tr("The passwords don't match.")); ui->body->setText(body + tr("The password doesn't match."));
}
else
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
ui->body->setText(body);
} }
// Password strength calculator ui->strengthBar->setValue(getPasswordStrength(pswd));
// Based on code in the Master Password dialog in Firefox }
// (pref-masterpass.js)
// Original code triple-licensed under the MPL, GPL, and LGPL
// so is license-compatible with this file
const double lengthFactor = reasonablePasswordLength / 8.0;
int pwlength = (int)(pswd.length() / lengthFactor);
if (pwlength > 5)
pwlength = 5;
const QRegExp numRxp("[0-9]", Qt::CaseSensitive, QRegExp::RegExp);
int numeric = (int)(pswd.count(numRxp) / lengthFactor);
if (numeric > 3)
numeric = 3;
const QRegExp symbRxp("\\W", Qt::CaseInsensitive, QRegExp::RegExp); int SetPasswordDialog::getPasswordStrength(QString pass)
int numsymbols = (int)(pswd.count(symbRxp) / lengthFactor); {
if (numsymbols > 3) if (pass.size() < 6)
numsymbols = 3; return 0;
const QRegExp upperRxp("[A-Z]", Qt::CaseSensitive, QRegExp::RegExp); double fscore = 0;
int upper = (int)(pswd.count(upperRxp) / lengthFactor); QHash<QChar, int> charCounts;
if (upper > 3) for (QChar c : pass)
upper = 3; {
charCounts[c]++;
fscore += 5. / charCounts[c];
}
int pwstrength=((pwlength*10)-20) + (numeric*10) + (numsymbols*15) + (upper*10); int variations = -1;
if (pwstrength < 0) variations += pass.contains(QRegExp("[0-9]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0;
pwstrength = 0; variations += pass.contains(QRegExp("[a-z]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0;
variations += pass.contains(QRegExp("[A-Z]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0;
variations += pass.contains(QRegExp("[\\W]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 0;
if (pwstrength > 100) int score = fscore;
pwstrength = 100; score += variations * 10;
score -= 20;
score = std::min(score, 100);
score = std::max(score, 0);
ui->strengthBar->setValue(pwstrength); return score;
} }
QString SetPasswordDialog::getPassword() QString SetPasswordDialog::getPassword()

1
src/widget/form/setpassworddialog.h

@ -30,6 +30,7 @@ public:
explicit SetPasswordDialog(QString body, QString extraButton, QWidget* parent = 0); explicit SetPasswordDialog(QString body, QString extraButton, QWidget* parent = 0);
~SetPasswordDialog(); ~SetPasswordDialog();
QString getPassword(); QString getPassword();
static int getPasswordStrength(QString pass);
private slots: private slots:
void onPasswordEdit(); void onPasswordEdit();

6
src/widget/form/settings/generalform.cpp

@ -20,6 +20,8 @@
#include "src/misc/smileypack.h" #include "src/misc/smileypack.h"
#include "src/core/core.h" #include "src/core/core.h"
#include "src/misc/style.h" #include "src/misc/style.h"
#include "src/nexus.h"
#include "src/profile.h"
#include <QMessageBox> #include <QMessageBox>
#include <QStyleFactory> #include <QStyleFactory>
#include <QTime> #include <QTime>
@ -349,9 +351,9 @@ void GeneralForm::onReconnectClicked()
{ {
if (Core::getInstance()->anyActiveCalls()) if (Core::getInstance()->anyActiveCalls())
QMessageBox::warning(this, tr("Call active", "popup title"), QMessageBox::warning(this, tr("Call active", "popup title"),
tr("You can't disconnect while a call is active!", "popup text")); tr("You can't disconnect while a call is active!", "popup text"));
else else
emit Widget::getInstance()->changeProfile(Settings::getInstance().getCurrentProfile()); Nexus::getProfile()->restartCore();
} }
void GeneralForm::reloadSmiles() void GeneralForm::reloadSmiles()

212
src/widget/form/settings/privacyform.cpp

@ -31,15 +31,8 @@ PrivacyForm::PrivacyForm() :
bodyUI = new Ui::PrivacySettings; bodyUI = new Ui::PrivacySettings;
bodyUI->setupUi(this); bodyUI->setupUi(this);
bodyUI->encryptToxHLayout->addStretch();
bodyUI->encryptLogsHLayout->addStretch();
connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated())); connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated()));
connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated())); connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated()));
connect(bodyUI->cbEncryptHistory, SIGNAL(clicked()), this, SLOT(onEncryptLogsUpdated()));
connect(bodyUI->changeLogsPwButton, &QPushButton::clicked, this, &PrivacyForm::setChatLogsPassword);
connect(bodyUI->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated()));
connect(bodyUI->changeToxPwButton, &QPushButton::clicked, this, &PrivacyForm::setToxPassword);
connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam())); connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam()));
connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam())); connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam()));
connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit())); connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit()));
@ -53,7 +46,6 @@ PrivacyForm::~PrivacyForm()
void PrivacyForm::onEnableLoggingUpdated() void PrivacyForm::onEnableLoggingUpdated()
{ {
Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked()); Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked());
bodyUI->cbEncryptHistory->setEnabled(bodyUI->cbKeepHistory->isChecked());
HistoryKeeper::resetInstance(); HistoryKeeper::resetInstance();
Widget::getInstance()->clearAllReceipts(); Widget::getInstance()->clearAllReceipts();
} }
@ -63,205 +55,6 @@ void PrivacyForm::onTypingNotificationEnabledUpdated()
Settings::getInstance().setTypingNotification(bodyUI->cbTypingNotification->isChecked()); Settings::getInstance().setTypingNotification(bodyUI->cbTypingNotification->isChecked());
} }
bool PrivacyForm::setChatLogsPassword()
{
Core* core = Core::getInstance();
SetPasswordDialog* dialog;
// check if an encrypted history exists because it was disabled earlier, and use it if possible
QString path = HistoryKeeper::getHistoryPath(QString(), 1);
QByteArray salt = core->getSaltFromFile(path);
bool haveEncHist = salt.size() > 0;
QString body = tr("Please set your new chat history password.");
if (haveEncHist)
body += "\n\n" + tr("It appears you have an unused encrypted chat history; if the password matches, it will be added to your current history.");
if (core->isPasswordSet(Core::ptMain))
dialog = new SetPasswordDialog(body, tr("Use data file password", "pushbutton text"), 0);
else
dialog = new SetPasswordDialog(body, QString(), 0);
do {
int r = dialog->exec();
if (r == QDialog::Rejected)
break;
QList<HistoryKeeper::HistMessage> oldMessages = HistoryKeeper::exportMessagesDeleteFile();
QString newpw = dialog->getPassword();
if (r == SetPasswordDialog::Tertiary)
core->useOtherPassword(Core::ptHistory);
else if (haveEncHist)
core->setPassword(newpw, Core::ptHistory, reinterpret_cast<uint8_t*>(salt.data()));
else
core->setPassword(newpw, Core::ptHistory);
if (!haveEncHist || HistoryKeeper::checkPassword(1))
{
Settings::getInstance().setEncryptLogs(true);
HistoryKeeper::getInstance()->importMessages(oldMessages);
if (haveEncHist)
{
Widget::getInstance()->reloadHistory();
GUI::showWarning(tr("Successfully decrypted old chat history","popup title"), tr("You have succesfully decrypted the old chat history, and it has been added to your current history and re-encrypted.", "popup text"));
}
delete dialog;
return true;
}
else
{
if (GUI::askQuestion(tr("Old encrypted chat history", "popup title"), tr("There is currently an unused encrypted chat history, but the password you just entered doesn't match.\n\nIf you don't care about the old history, you may delete it and use the password you just entered.\nOtherwise, hit Cancel to try again.", "This happens when enabling encryption after previously \"Disabling History\""), tr("Delete"), tr("Cancel")))
{
if (GUI::askQuestion(tr("Old encrypted chat history", "popup title"), tr("Are you absolutely sure you want to lose the unused encrypted chat history?", "secondary popup"), tr("Delete"), tr("Cancel")))
haveEncHist = false; // logically this is really just a `break`, but conceptually this is more accurate
}
}
} while (haveEncHist);
delete dialog;
return false;
}
void PrivacyForm::onEncryptLogsUpdated()
{
Core* core = Core::getInstance();
if (bodyUI->cbEncryptHistory->isChecked())
{
if (!core->isPasswordSet(Core::ptHistory))
{
if (setChatLogsPassword())
{
bodyUI->cbEncryptHistory->setChecked(true);
bodyUI->changeLogsPwButton->setEnabled(true);
return;
}
}
}
else
{
QMessageBox box(QMessageBox::Warning,
tr("Old encrypted chat history", "title"),
tr("Would you like to decrypt your chat history?\nOtherwise it will be deleted."),
QMessageBox::NoButton, Widget::getInstance());
QPushButton* decryptBtn = box.addButton(tr("Decrypt"), QMessageBox::YesRole);
QPushButton* deleteBtn = box.addButton(tr("Delete"), QMessageBox::NoRole);
QPushButton* cancelBtn = box.addButton(tr("Cancel"), QMessageBox::RejectRole);
box.setDefaultButton(cancelBtn);
box.setEscapeButton(cancelBtn);
box.exec();
if (box.clickedButton() == decryptBtn)
{
QList<HistoryKeeper::HistMessage> oldMessages = HistoryKeeper::exportMessagesDeleteFile(true);
core->clearPassword(Core::ptHistory);
Settings::getInstance().setEncryptLogs(false);
HistoryKeeper::getInstance()->importMessages(oldMessages);
}
else if (box.clickedButton() == deleteBtn)
{
QMessageBox box2(QMessageBox::Critical,
tr("Old encrypted chat history", "title"),
tr("Are you sure you want to lose your entire chat history?"),
QMessageBox::NoButton, Widget::getInstance());
QPushButton* deleteBtn2 = box2.addButton(tr("Delete"), QMessageBox::AcceptRole);
QPushButton* cancelBtn2 = box2.addButton(tr("Cancel"), QMessageBox::RejectRole);
box2.setDefaultButton(cancelBtn2);
box2.setEscapeButton(cancelBtn2);
box2.exec();
if (box2.clickedButton() == deleteBtn2)
{
HistoryKeeper::removeHistory(true);
}
else
{
bodyUI->cbEncryptHistory->setChecked(true);
return;
}
}
else
{
bodyUI->cbEncryptHistory->setChecked(true);
return;
}
}
core->clearPassword(Core::ptHistory);
Settings::getInstance().setEncryptLogs(false);
bodyUI->cbEncryptHistory->setChecked(false);
bodyUI->changeLogsPwButton->setEnabled(false);
HistoryKeeper::resetInstance();
}
bool PrivacyForm::setToxPassword()
{
Core* core = Core::getInstance();
SetPasswordDialog* dialog;
QString body = tr("Please set your new data file password.");
if (core->isPasswordSet(Core::ptHistory))
dialog = new SetPasswordDialog(body, tr("Use chat history password", "pushbutton text"), 0);
else
dialog = new SetPasswordDialog(body, QString(), 0);
if (int r = dialog->exec())
{
QString newpw = dialog->getPassword();
delete dialog;
if (r == SetPasswordDialog::Tertiary)
core->useOtherPassword(Core::ptMain);
else
core->setPassword(newpw, Core::ptMain);
Settings::getInstance().setEncryptTox(true);
core->saveConfiguration();
return true;
}
else
{
delete dialog;
return false;
}
}
void PrivacyForm::onEncryptToxUpdated()
{
Core* core = Core::getInstance();
if (bodyUI->cbEncryptTox->isChecked())
{
if (!core->isPasswordSet(Core::ptMain))
{
if (setToxPassword())
{
bodyUI->cbEncryptTox->setChecked(true);
bodyUI->changeToxPwButton->setEnabled(true);
return;
}
}
}
else
{
if (!GUI::askQuestion(tr("Decrypt your data file", "title"),
tr("Would you like to decrypt your data file?"),
tr("Decrypt"), tr("Cancel")))
{
bodyUI->cbEncryptTox->setChecked(true);
return;
}
// affirmative answer falls through to the catch all below
}
bodyUI->cbEncryptTox->setChecked(false);
Settings::getInstance().setEncryptTox(false);
bodyUI->changeToxPwButton->setEnabled(false);
core->clearPassword(Core::ptMain);
}
void PrivacyForm::setNospam() void PrivacyForm::setNospam()
{ {
QString newNospam = bodyUI->nospamLineEdit->text(); QString newNospam = bodyUI->nospamLineEdit->text();
@ -277,11 +70,6 @@ void PrivacyForm::present()
bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam); bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam);
bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled()); bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled());
bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging()); bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs());
bodyUI->changeLogsPwButton->setEnabled(Settings::getInstance().getEncryptLogs());
bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging());
bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox());
bodyUI->changeToxPwButton->setEnabled(Settings::getInstance().getEncryptTox());
} }
void PrivacyForm::generateRandomNospam() void PrivacyForm::generateRandomNospam()

4
src/widget/form/settings/privacyform.h

@ -36,10 +36,6 @@ private slots:
void setNospam(); void setNospam();
void generateRandomNospam(); void generateRandomNospam();
void onNospamEdit(); void onNospamEdit();
void onEncryptLogsUpdated();
bool setChatLogsPassword();
void onEncryptToxUpdated();
bool setToxPassword();
private: private:
Ui::PrivacySettings* bodyUI; Ui::PrivacySettings* bodyUI;

85
src/widget/form/settings/privacysettings.ui

@ -37,7 +37,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>380</width> <width>380</width>
<height>391</height> <height>280</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
@ -47,7 +47,7 @@
<string comment="tooltip for typing notifications setting">Your friends will be able to see when you are typing.</string> <string comment="tooltip for typing notifications setting">Your friends will be able to see when you are typing.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Send Typing Notifications</string> <string>Send typing notifications</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -58,89 +58,32 @@
Save format changes are possible, which may result in data loss.</string> Save format changes are possible, which may result in data loss.</string>
</property> </property>
<property name="text"> <property name="text">
<string>Keep chat history (mostly stable)</string> <string>Keep chat history</string>
</property> </property>
</widget> </widget>
</item> </item>
<item alignment="Qt::AlignTop"> <item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="encryptionGroup"> <widget class="QGroupBox" name="nospamGroup">
<property name="enabled"> <property name="toolTip">
<bool>true</bool> <string comment="toolTip for nospam">Nospam is part of your Tox ID.
It is there to help you change your Tox ID when you feel like you are getting too much spam friend requests.
When you change nospam, your current contacts still can communicate with you,
but new contacts need to know your new Tox ID to be able to add you.</string>
</property> </property>
<property name="title"> <property name="title">
<string>Local file encryption</string> <string>NoSpam</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
<widget class="QLabel" name="encryptStatement"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>All Tox communications over the internet are encrypted, and this cannot be disabled. However, you may optionally password protect your local Tox files.</string> <string>The NoSpam is part of your Tox ID, if you are getting spammed with friend requests, change the NoSpam.</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="encryptToxHLayout">
<item>
<widget class="QCheckBox" name="cbEncryptTox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypt Tox data file</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="changeToxPwButton">
<property name="text">
<string>Change password</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="encryptLogsHLayout">
<item>
<widget class="QCheckBox" name="cbEncryptHistory">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Encrypt chat history</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="changeLogsPwButton">
<property name="text">
<string>Change password</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="nospamGroup">
<property name="toolTip">
<string comment="toolTip for nospam">Nospam is part of your Tox ID.
It is there to help you change your Tox ID when you feel like you are getting too much spam friend requests.
When you change nospam, your current contacts still can communicate with you,
but new contacts need to know your new Tox ID to be able to add you.</string>
</property>
<property name="title">
<string>Nospam</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
@ -153,7 +96,7 @@ but new contacts need to know your new Tox ID to be able to add you.</string>
<item> <item>
<widget class="QPushButton" name="randomNosapamButton"> <widget class="QPushButton" name="randomNosapamButton">
<property name="text"> <property name="text">
<string>Generate random nospam</string> <string>Generate random NoSpam</string>
</property> </property>
</widget> </widget>
</item> </item>

16
src/widget/gui.cpp

@ -35,6 +35,14 @@ GUI& GUI::getInstance()
// Implementation of the public clean interface // Implementation of the public clean interface
void GUI::clearContacts()
{
if (QThread::currentThread() == qApp->thread())
getInstance()._clearContacts();
else
QMetaObject::invokeMethod(&getInstance(), "_clearContacts", Qt::BlockingQueuedConnection);
}
void GUI::setEnabled(bool state) void GUI::setEnabled(bool state)
{ {
if (QThread::currentThread() == qApp->thread()) if (QThread::currentThread() == qApp->thread())
@ -192,6 +200,14 @@ QString GUI::passwordDialog(const QString& cancel, const QString& body)
// Private implementations // Private implementations
void GUI::_clearContacts()
{
#ifdef Q_OS_ANDROID
#else
Nexus::getDesktopGUI()->clearContactsList();
#endif
}
void GUI::_setEnabled(bool state) void GUI::_setEnabled(bool state)
{ {
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID

3
src/widget/gui.h

@ -15,6 +15,8 @@ public:
static GUI& getInstance(); static GUI& getInstance();
/// Returns the main QWidget* of the application /// Returns the main QWidget* of the application
static QWidget* getMainWidget(); static QWidget* getMainWidget();
/// Clear the GUI's contact list
static void clearContacts();
/// Will enable or disable the GUI. /// Will enable or disable the GUI.
/// A disabled GUI can't be interacted with by the user /// A disabled GUI can't be interacted with by the user
static void setEnabled(bool state); static void setEnabled(bool state);
@ -64,6 +66,7 @@ private:
// Private implementation, those must be called from the GUI thread // Private implementation, those must be called from the GUI thread
private slots: private slots:
void _clearContacts();
void _setEnabled(bool state); void _setEnabled(bool state);
void _setWindowTitle(const QString& title); void _setWindowTitle(const QString& title);
void _reloadTheme(); void _reloadTheme();

167
src/widget/loginscreen.cpp

@ -0,0 +1,167 @@
#include "loginscreen.h"
#include "ui_loginscreen.h"
#include "src/profile.h"
#include "src/profilelocker.h"
#include "src/nexus.h"
#include "src/misc/settings.h"
#include "src/widget/form/setpassworddialog.h"
#include <QMessageBox>
#include <QDebug>
LoginScreen::LoginScreen(QWidget *parent) :
QWidget(parent),
ui(new Ui::LoginScreen)
{
ui->setupUi(this);
connect(ui->newProfilePgbtn, &QPushButton::clicked, this, &LoginScreen::onNewProfilePageClicked);
connect(ui->loginPgbtn, &QPushButton::clicked, this, &LoginScreen::onLoginPageClicked);
connect(ui->createAccountButton, &QPushButton::clicked, this, &LoginScreen::onCreateNewProfile);
connect(ui->newUsername, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile);
connect(ui->newPass, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile);
connect(ui->newPassConfirm, &QLineEdit::returnPressed, this, &LoginScreen::onCreateNewProfile);
connect(ui->loginButton, &QPushButton::clicked, this, &LoginScreen::onLogin);
connect(ui->loginUsernames, &QComboBox::currentTextChanged, this, &LoginScreen::onLoginUsernameSelected);
connect(ui->loginPassword, &QLineEdit::returnPressed, this, &LoginScreen::onLogin);
connect(ui->newPass, &QLineEdit::textChanged, this, &LoginScreen::onPasswordEdited);
connect(ui->newPassConfirm, &QLineEdit::textChanged, this, &LoginScreen::onPasswordEdited);
reset();
}
LoginScreen::~LoginScreen()
{
delete ui;
}
void LoginScreen::reset()
{
ui->newUsername->clear();
ui->newPass->clear();
ui->newPassConfirm->clear();
ui->loginPassword->clear();
ui->loginUsernames->clear();
Profile::scanProfiles();
QString lastUsed = Settings::getInstance().getCurrentProfile();
qDebug() << "Last used is "<<lastUsed;
QVector<QString> profiles = Profile::getProfiles();
for (QString profile : profiles)
{
ui->loginUsernames->addItem(profile);
if (profile == lastUsed)
ui->loginUsernames->setCurrentIndex(ui->loginUsernames->count()-1);
}
if (profiles.isEmpty())
ui->stackedWidget->setCurrentIndex(0);
else
ui->stackedWidget->setCurrentIndex(1);
}
void LoginScreen::onNewProfilePageClicked()
{
ui->stackedWidget->setCurrentIndex(0);
}
void LoginScreen::onLoginPageClicked()
{
ui->stackedWidget->setCurrentIndex(1);
}
void LoginScreen::onCreateNewProfile()
{
QString name = ui->newUsername->text();
QString pass = ui->newPass->text();
if (name.isEmpty())
{
QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The username must not be empty."));
return;
}
if (pass.size()!=0 && pass.size() < 6)
{
QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The password must be at least 6 characters."));
return;
}
if (ui->newPassConfirm->text() != pass)
{
QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("The passwords are different."));
return;
}
if (Profile::profileExists(name))
{
QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("This profile already exists."));
return;
}
Profile* profile = Profile::createProfile(name, pass);
if (!profile)
{
// Unknown error
QMessageBox::critical(this, tr("Couldn't create a new profile"), tr("Couldn't create a new profile."));
return;
}
Nexus& nexus = Nexus::getInstance();
nexus.setProfile(profile);
nexus.showMainGUI();
}
void LoginScreen::onLoginUsernameSelected(const QString &name)
{
if (name.isEmpty())
return;
ui->loginPassword->clear();
if (Profile::isEncrypted(name))
{
ui->loginPasswordLabel->show();
ui->loginPassword->show();
}
else
{
ui->loginPasswordLabel->hide();
ui->loginPassword->hide();
}
}
void LoginScreen::onLogin()
{
QString name = ui->loginUsernames->currentText();
QString pass = ui->loginPassword->text();
if (!ProfileLocker::isLockable(name))
{
QMessageBox::critical(this, tr("Couldn't load this profile"), tr("This profile is already in use."));
return;
}
Profile* profile = Profile::loadProfile(name, pass);
if (!profile)
{
// Unknown error
QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Couldn't load this profile."));
return;
}
if (!profile->checkPassword())
{
QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Wrong password."));
delete profile;
return;
}
Nexus& nexus = Nexus::getInstance();
nexus.setProfile(profile);
nexus.showMainGUI();
}
void LoginScreen::onPasswordEdited()
{
ui->passStrengthMeter->setValue(SetPasswordDialog::getPasswordStrength(ui->newPass->text()));
}

33
src/widget/loginscreen.h

@ -0,0 +1,33 @@
#ifndef LOGINSCREEN_H
#define LOGINSCREEN_H
#include <QWidget>
namespace Ui {
class LoginScreen;
}
class LoginScreen : public QWidget
{
Q_OBJECT
public:
explicit LoginScreen(QWidget *parent = 0);
~LoginScreen();
void reset(); ///< Resets the UI, clears all fields
private slots:
void onLoginUsernameSelected(const QString& name);
void onPasswordEdited();
// Buttons to change page
void onNewProfilePageClicked();
void onLoginPageClicked();
// Buttons to submit form
void onCreateNewProfile();
void onLogin();
private:
Ui::LoginScreen *ui;
};
#endif // LOGINSCREEN_H

1109
src/widget/loginscreen.ui

File diff suppressed because it is too large Load Diff

2
src/widget/widget.cpp

@ -898,6 +898,8 @@ void Widget::removeFriend(int friendId)
void Widget::clearContactsList() void Widget::clearContactsList()
{ {
assert(QThread::currentThread() == qApp->thread());
QList<Friend*> friends = FriendList::getAllFriends(); QList<Friend*> friends = FriendList::getAllFriends();
for (Friend* f : friends) for (Friend* f : friends)
removeFriend(f, true); removeFriend(f, true);

3
src/widget/widget.h

@ -70,9 +70,9 @@ public:
void newMessageAlert(GenericChatroomWidget* chat); void newMessageAlert(GenericChatroomWidget* chat);
bool isFriendWidgetCurActiveWidget(Friend* f); bool isFriendWidgetCurActiveWidget(Friend* f);
bool getIsWindowMinimized(); bool getIsWindowMinimized();
void clearContactsList();
void setTranslation(); void setTranslation();
void updateIcons(); void updateIcons();
void clearContactsList();
~Widget(); ~Widget();
virtual void closeEvent(QCloseEvent *event); virtual void closeEvent(QCloseEvent *event);
@ -131,7 +131,6 @@ signals:
void statusSelected(Status status); void statusSelected(Status status);
void usernameChanged(const QString& username); void usernameChanged(const QString& username);
void statusMessageChanged(const QString& statusMessage); void statusMessageChanged(const QString& statusMessage);
void changeProfile(const QString& profile);
void resized(); void resized();
private slots: private slots:

Loading…
Cancel
Save