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

Merge branch 'loginscreen'

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

Fixes #1776
Fixes #1746
Fixes #1596
Fixes #1562
This commit is contained in:
tux3 2015-06-04 21:52:45 +02:00
commit 3878f88d06
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
33 changed files with 2219 additions and 1188 deletions

1
img/login_logo.svg Normal file
View File

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

View File

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

View File

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

View File

@ -24,11 +24,13 @@
#include "src/audio.h"
#include "src/profilelocker.h"
#include "src/avatarbroadcaster.h"
#include "src/profile.h"
#include "corefile.h"
#include <tox/tox.h>
#include <ctime>
#include <cassert>
#include <limits>
#include <functional>
@ -52,19 +54,15 @@ QThread* Core::coreThread{nullptr};
#define MAX_GROUP_MESSAGE_LEN 1024
Core::Core(QThread *CoreThread, QString loadPath) :
tox(nullptr), toxav(nullptr), loadPath(loadPath), ready{false}
Core::Core(QThread *CoreThread, Profile& profile) :
tox(nullptr), toxav(nullptr), profile(profile), ready{false}
{
qDebug() << "loading Tox from" << loadPath;
coreThread = CoreThread;
Audio::getInstance();
videobuf = nullptr;
for (int i = 0; i < ptCounter; i++)
pwsaltedkeys[i] = nullptr;
encryptionKey = nullptr;
toxTimer = new QTimer(this);
toxTimer->setSingleShot(true);
@ -104,8 +102,14 @@ Core::~Core()
{
qDebug() << "Deleting Core";
saveConfiguration();
toxTimer->stop();
if (coreThread->isRunning())
{
if (QThread::currentThread() == coreThread)
killTimers(false);
else
QMetaObject::invokeMethod(this, "killTimers", Qt::BlockingQueuedConnection,
Q_ARG(bool, false));
}
coreThread->exit(0);
while (coreThread->isRunning())
{
@ -127,7 +131,7 @@ Core* Core::getInstance()
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.
bool enableIPv6 = Settings::getInstance().getEnableIPv6();
@ -237,19 +241,25 @@ void Core::make_tox(QByteArray savedata)
void Core::start()
{
qDebug() << "Starting up";
QByteArray savedata = loadToxSave(loadPath);
make_tox(savedata);
// Do we need to create a new save & profile?
if (savedata.isNull())
bool isNewProfile = profile.isNewProfile();
if (isNewProfile)
{
qDebug() << "Save file not found, creating a new profile";
qDebug() << "Creating a new profile";
makeTox(QByteArray());
Settings::getInstance().load();
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));
@ -275,7 +285,7 @@ void Core::start()
emit idSet(id);
// tox core is already decrypted
if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs())
if (Settings::getInstance().getEnableLogging() && Nexus::getProfile()->isEncrypted())
checkEncryptedHistory();
loadFriends();
@ -324,7 +334,7 @@ void Core::start()
}
else
{
qDebug() << "Error loading self avatar";
qDebug() << "Self avatar not found";
}
ready = true;
@ -332,9 +342,9 @@ void Core::start()
// If we created a new profile earlier,
// 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.
if (savedata.isNull())
if (isNewProfile)
{
saveConfiguration();
profile.saveToxSave();
emit idSet(getSelfId().toString());
}
@ -577,7 +587,7 @@ void Core::acceptFriendRequest(const QString& userId)
}
else
{
saveConfiguration();
profile.saveToxSave();
emit friendAdded(friendId, userId);
}
}
@ -622,7 +632,7 @@ void Core::requestFriendship(const QString& friendAddress, const QString& messag
emit friendAdded(friendId, userId);
}
}
saveConfiguration();
profile.saveToxSave();
}
int Core::sendMessage(uint32_t friendId, const QString& message)
@ -737,7 +747,7 @@ void Core::removeFriend(uint32_t friendId, bool fake)
}
else
{
saveConfiguration();
profile.saveToxSave();
emit friendRemoved(friendId);
}
}
@ -775,7 +785,8 @@ void Core::setUsername(const QString& username)
else
{
emit usernameSet(username);
saveConfiguration();
if (ready)
profile.saveToxSave();
}
}
@ -839,7 +850,8 @@ void Core::setStatusMessage(const QString& message)
}
else
{
saveConfiguration();
if (ready)
profile.saveToxSave();
emit statusMessageSet(message);
}
}
@ -864,7 +876,7 @@ void Core::setStatus(Status status)
}
tox_self_set_status(tox, userstatus);
saveConfiguration();
profile.saveToxSave();
emit statusSet(status);
}
@ -885,151 +897,15 @@ QString Core::sanitize(QString name)
return name;
}
QByteArray Core::loadToxSave(QString path)
QByteArray Core::getToxSaveData()
{
uint32_t fileSize = tox_get_savedata_size(tox);
QByteArray data;
loadPath = ""; // if not empty upon return, then user forgot a password and is switching
// 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();
data.resize(fileSize);
tox_get_savedata(tox, (uint8_t*)data.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()
{
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();
}

View File

@ -29,6 +29,7 @@
#include "coredefines.h"
#include "toxid.h"
class Profile;
template <typename T> class QList;
class QTimer;
class QString;
@ -43,9 +44,7 @@ class Core : public QObject
{
Q_OBJECT
public:
enum PasswordType {ptMain = 0, ptHistory, ptCounter};
explicit Core(QThread* coreThread, QString initialLoadPath);
explicit Core(QThread* coreThread, Profile& profile);
static Core* getInstance(); ///< Returns the global widget's Core instance
~Core();
@ -81,19 +80,18 @@ public:
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)
bool isPasswordSet(PasswordType passtype);
bool isPasswordSet();
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
public slots:
void start(); ///< Initializes the core, must be called before anything else
void reset(); ///< Reinitialized the core. Must be called from the Core thread, with the GUI thread ready to process events.
void process(); ///< Processes toxcore events and ensure we stay connected, called by its own timer
void bootstrapDht(); ///< Connects us to the Tox network
void saveConfiguration();
void saveConfiguration(const QString& path);
void switchConfiguration(const QString& profile); ///< Load a different profile and restart the core
QByteArray getToxSaveData(); ///< Returns the unencrypted tox save data
void acceptFriendRequest(const QString& userId);
void requestFriendship(const QString& friendAddress, const QString& message);
@ -146,16 +144,14 @@ public slots:
static bool isGroupCallMicEnabled(int groupId);
static bool isGroupCallVolEnabled(int groupId);
void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr);
void useOtherPassword(PasswordType type);
void clearPassword(PasswordType passtype);
QByteArray encryptData(const QByteArray& data, PasswordType passtype);
QByteArray decryptData(const QByteArray& data, PasswordType passtype);
void setPassword(const QString &password, uint8_t* salt = nullptr);
void clearPassword();
QByteArray encryptData(const QByteArray& data);
QByteArray decryptData(const QByteArray& data);
signals:
void connected();
void disconnected();
void blockingClearContacts();
void friendRequestReceived(const QString& userId, const QString& message);
void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction);
@ -278,22 +274,22 @@ private:
bool checkConnection();
QByteArray loadToxSave(QString path);
bool loadEncryptedSave(QByteArray& data);
void checkEncryptedHistory();
void make_tox(QByteArray savedata);
void makeTox(QByteArray savedata);
void loadFriends();
void checkLastOnline(uint32_t friendId);
void deadifyTox();
private slots:
void killTimers(bool onlyStop); ///< Must only be called from the Core thread
private:
Tox* tox;
ToxAv* toxav;
QTimer *toxTimer, *fileTimer; //, *saveTimer;
QString loadPath; // meaningless after start() is called
int dhtServerId;
QTimer *toxTimer;
Profile& profile;
static ToxCall calls[TOXAV_MAX_CALLS];
#ifdef QTOX_FILTER_AUDIO
static AudioFilterer * filterer[TOXAV_MAX_CALLS];
@ -302,17 +298,7 @@ private:
QMutex messageSendMutex;
bool ready;
TOX_PASS_KEY* pwsaltedkeys[PasswordType::ptCounter] = {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();
TOX_PASS_KEY* encryptionKey = nullptr; // use the pw's hash as the "pw"
static const int videobufsize;
static uint8_t* videobuf;

View File

@ -21,6 +21,8 @@
#include "src/misc/settings.h"
#include "src/misc/cstring.h"
#include "src/historykeeper.h"
#include "src/nexus.h"
#include "src/profile.h"
#include <tox/tox.h>
#include <tox/toxencryptsave.h>
#include <QApplication>
@ -31,113 +33,67 @@
#include <algorithm>
#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())
return;
pwsaltedkeys[passtype] = new TOX_PASS_KEY;
encryptionKey = new TOX_PASS_KEY;
CString str(password);
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
tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype], nullptr);
password.clear();
tox_derive_key_from_pass(str.data(), str.size(), encryptionKey, nullptr);
}
void Core::useOtherPassword(PasswordType type)
void Core::clearPassword()
{
clearPassword(type);
pwsaltedkeys[type] = new TOX_PASS_KEY;
PasswordType other = (type == ptMain) ? ptHistory : ptMain;
std::copy(pwsaltedkeys[other], pwsaltedkeys[other]+1, pwsaltedkeys[type]);
delete encryptionKey;
encryptionKey = nullptr;
}
void Core::clearPassword(PasswordType passtype)
QByteArray Core::encryptData(const QByteArray& data)
{
delete pwsaltedkeys[passtype];
pwsaltedkeys[passtype] = nullptr;
}
// part of a hack, see core.h
void Core::saveCurrentInformation()
{
if (pwsaltedkeys[ptMain])
if (!encryptionKey)
{
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()
{
QString out;
if (backupProfile)
{
out = *backupProfile;
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])
qWarning() << "No encryption key set";
return QByteArray();
}
uint8_t encrypted[data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH];
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(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();
}
int sz = data.size() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
uint8_t decrypted[sz];
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(reinterpret_cast<char*>(decrypted), sz);
}
bool Core::isPasswordSet(PasswordType passtype)
bool Core::isPasswordSet()
{
if (pwsaltedkeys[passtype])
return true;
return false;
return static_cast<bool>(encryptionKey);
}
QByteArray Core::getSaltFromFile(QString filename)
@ -163,110 +119,33 @@ QByteArray Core::getSaltFromFile(QString filename)
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()
{
QString path = HistoryKeeper::getHistoryPath();
bool exists = QFile::exists(path);
bool exists = QFile::exists(path) && QFile(path).size()>0;
QByteArray salt = getSaltFromFile(path);
if (exists && salt.size() == 0)
{ // 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!"));
Settings::getInstance().setEncryptLogs(false);
Settings::getInstance().setEnableLogging(false);
HistoryKeeper::resetInstance();
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 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;
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");
}
else
{
dialogtxt = a;
}
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");
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;
do
{
@ -274,87 +153,17 @@ void Core::checkEncryptedHistory()
if (pw.isEmpty())
{
clearPassword(ptHistory);
Settings::getInstance().setEncryptLogs(false);
clearPassword();
Settings::getInstance().setEnableLogging(false);
HistoryKeeper::resetInstance();
return;
}
else
{
setPassword(pw, ptHistory, reinterpret_cast<uint8_t*>(salt.data()));
setPassword(pw, reinterpret_cast<uint8_t*>(salt.data()));
}
error = exists && !HistoryKeeper::checkPassword();
dialogtxt = a + "\n" + c + "\n" + b;
} 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();
}

View File

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

View File

@ -12,6 +12,7 @@
See the COPYING file for more details.
*/
#include "toxme.h"
#include "widget/widget.h"
#include "misc/settings.h"
#include "src/nexus.h"
@ -19,7 +20,9 @@
#include "src/widget/toxuri.h"
#include "src/widget/toxsave.h"
#include "src/autoupdate.h"
#include "src/profile.h"
#include "src/profilelocker.h"
#include "src/widget/loginscreen.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QDateTime>
@ -31,9 +34,6 @@
#include <QProcess>
#include <sodium.h>
#include "toxme.h"
#include <unistd.h>
#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"))
{
QString profile = parser.value("p");
if (QDir(Settings::getSettingsDirPath()).exists(profile + ".tox"))
QString profileName = parser.value("p");
if (QDir(Settings::getSettingsDirPath()).exists(profileName + ".tox"))
{
qDebug() << "Setting profile to" << profile;
Settings::getInstance().switchProfile(profile);
qDebug() << "Setting profile to" << profileName;
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
{
qCritical() << "-p profile" << profile + ".tox" << "doesn't exist";
qCritical() << "-p profile" << profileName + ".tox" << "doesn't exist";
return EXIT_FAILURE;
}
}
@ -292,7 +305,6 @@ int main(int argc, char *argv[])
Nexus::getInstance().start();
// Run
a.setQuitOnLastWindowClosed(false);
int errorcode = a.exec();
#ifdef LOG_TO_FILE

View File

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

View File

@ -40,8 +40,7 @@
#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true
const QString Settings::OLDFILENAME = "settings.ini";
const QString Settings::FILENAME = "qtox.ini";
const QString Settings::globalSettingsFile = "qtox.ini";
Settings* Settings::settings{nullptr};
bool Settings::makeToxPortable{false};
@ -59,130 +58,17 @@ Settings& Settings::getInstance()
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()
{
if (loaded)
return;
createSettingsDir();
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");
makeToxPortable = ps.value("makeToxPortable", false).toBool();
ps.endGroup();
@ -192,16 +78,13 @@ void Settings::load()
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 = dir.filePath(OLDFILENAME)).exists())
{
qDebug() << "No settings file found, using defaults";
filePath = ":/conf/" + FILENAME;
}
qDebug() << "No settings file found, using defaults";
filePath = ":/conf/" + globalSettingsFile;
}
qDebug() << "Loading settings from " + filePath;
@ -366,15 +249,13 @@ void Settings::load()
ps.beginGroup("Privacy");
typingNotification = ps.value("typingNotification", false).toBool();
enableLogging = ps.value("enableLogging", false).toBool();
encryptLogs = ps.value("encryptLogs", false).toBool();
encryptTox = ps.value("encryptTox", false).toBool();
ps.endGroup();
}
}
void Settings::save(bool writePersonal)
{
QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME);
QString filePath = QDir(getSettingsDirPath()).filePath(globalSettingsFile);
save(filePath, writePersonal);
}
@ -513,8 +394,6 @@ void Settings::savePersonal(QString path)
ps.beginGroup("Privacy");
ps.setValue("typingNotification", typingNotification);
ps.setValue("enableLogging", enableLogging);
ps.setValue("encryptLogs", encryptLogs);
ps.setValue("encryptTox", encryptTox);
ps.endGroup();
}
@ -625,7 +504,7 @@ bool Settings::getMakeToxPortable() const
void Settings::setMakeToxPortable(bool 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
save();
}
@ -848,26 +727,6 @@ void Settings::setEnableLogging(bool 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
{
return dbSyncType;
@ -1246,7 +1105,6 @@ bool Settings::getCompactLayout() const
void Settings::setCompactLayout(bool value)
{
compactLayout = value;
emit compactLayoutChanged();
}
bool Settings::getGroupchatPosition() const
@ -1268,3 +1126,26 @@ void Settings::setThemeColor(const int &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;
}

View File

@ -30,14 +30,11 @@ class Settings : public QObject
{
Q_OBJECT
public:
static Settings& getInstance();
void switchProfile(const QString& profile);
QString detectProfile();
QList<QString> searchProfiles();
QString askProfiles();
~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();
@ -107,12 +104,6 @@ public:
bool getEnableLogging() const;
void setEnableLogging(bool newValue);
bool getEncryptLogs() const;
void setEncryptLogs(bool newValue);
bool getEncryptTox() const;
void setEncryptTox(bool newValue);
Db::syncType getDbSyncType() const;
void setDbSyncType(int newValue);
@ -259,9 +250,6 @@ public:
void save(QString path, bool writePersonal = true);
void load();
private:
static QString genRandomProfileName();
private:
static Settings* settings;
@ -272,7 +260,7 @@ private:
void saveGlobal(QString path);
void savePersonal(QString path);
static const QString FILENAME;
static const QString globalSettingsFile;
static const QString OLDFILENAME;
bool loaded;
@ -309,8 +297,6 @@ private:
uint32_t currentProfileId;
bool enableLogging;
bool encryptLogs;
bool encryptTox = false;
int autoAwayTime;
@ -365,12 +351,9 @@ private:
int themeColor;
signals:
//void dataChanged();
void dhtServerListChanged();
void logStorageOptsChanged();
void smileyPackChanged();
void emojiFontChanged();
void compactLayoutChanged();
};
#endif // SETTINGS_HPP

View File

@ -1,47 +1,50 @@
#include "nexus.h"
#include "profile.h"
#include "src/core/core.h"
#include "misc/settings.h"
#include "video/camerasource.h"
#include "widget/gui.h"
#include "widget/loginscreen.h"
#include <QThread>
#include <QDebug>
#include <QImageReader>
#include <QFile>
#include <QApplication>
#include <cassert>
#ifdef Q_OS_ANDROID
#include <src/widget/androidgui.h>
#else
#include <src/widget/widget.h>
#include <QDesktopWidget>
#endif
static Nexus* nexus{nullptr};
Nexus::Nexus(QObject *parent) :
QObject(parent),
core{nullptr},
coreThread{nullptr},
profile{nullptr},
widget{nullptr},
androidgui{nullptr},
started{false}
loginScreen{nullptr}
{
}
Nexus::~Nexus()
{
delete core;
delete coreThread;
#ifdef Q_OS_ANDROID
delete androidgui;
#else
delete widget;
#endif
delete loginScreen;
delete profile;
if (profile)
Settings::getInstance().save();
}
void Nexus::start()
{
if (started)
return;
qDebug() << "Starting up";
// Setup the environment
@ -56,22 +59,49 @@ void Nexus::start()
qRegisterMetaType<QPixmap>("QPixmap");
qRegisterMetaType<ToxFile>("ToxFile");
qRegisterMetaType<ToxFile::FileDirection>("ToxFile::FileDirection");
qRegisterMetaType<Core::PasswordType>("Core::PasswordType");
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
#ifndef Q_OS_ANDROID
widget = Widget::getInstance();
#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
#ifdef Q_OS_ANDROID
androidgui = new AndroidGUI;
@ -88,6 +118,7 @@ void Nexus::start()
GUI::setEnabled(false);
// Connections
Core* core = profile->getCore();
#ifdef Q_OS_ANDROID
connect(core, &Core::connected, androidgui, &AndroidGUI::onConnected);
connect(core, &Core::disconnected, androidgui, &AndroidGUI::onDisconnected);
@ -126,7 +157,6 @@ void Nexus::start()
connect(core, &Core::groupPeerAudioPlaying, widget, &Widget::onGroupPeerAudioPlaying);
connect(core, &Core::emptyGroupCreated, widget, &Widget::onEmptyGroupCreated);
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::messageSentResult, widget, &Widget::onMessageSendResult);
@ -135,13 +165,9 @@ void Nexus::start()
connect(widget, &Widget::statusSet, core, &Core::setStatus);
connect(widget, &Widget::friendRequested, core, &Core::requestFriendship);
connect(widget, &Widget::friendRequestAccepted, core, &Core::acceptFriendRequest);
connect(widget, &Widget::changeProfile, core, &Core::switchConfiguration);
#endif
// Start Core
coreThread->start();
started = true;
profile->startCore();
}
Nexus& Nexus::getInstance()
@ -160,7 +186,20 @@ void Nexus::destroyInstance()
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()

View File

@ -3,10 +3,11 @@
#include <QObject>
class QThread;
class Core;
class Widget;
class AndroidGUI;
class Profile;
class LoginScreen;
class Core;
/// This class is in charge of connecting various systems together
/// and forwarding signals appropriately to the right objects
@ -15,11 +16,17 @@ class Nexus : public QObject
{
Q_OBJECT
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 void destroyInstance();
static Core* getCore(); ///< Will return 0 if not started
static Profile* getProfile(); ///< Will return 0 if not started
static void setProfile(Profile* profile); ///< Delete the current profile, if any, and replaces it
static AndroidGUI* getAndroidGUI(); ///< Will return 0 if not started
static Widget* getDesktopGUI(); ///< Will return 0 if not started
static QString getSupportedImageFilter();
@ -30,11 +37,10 @@ private:
~Nexus();
private:
Core* core;
QThread* coreThread;
Profile* profile;
Widget* widget;
AndroidGUI* androidgui;
bool started;
LoginScreen* loginScreen;
};
#endif // NEXUS_H

339
src/profile.cpp Normal file
View File

@ -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 Normal file
View File

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

View File

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

View File

@ -24,6 +24,10 @@ public:
static bool lock(QString profile);
/// Releases the lock on the current profile
static void unlock();
/// Returns true if we're currently holding a lock
static bool hasLock();
/// Return the name of the currently loaded profile, a null string if there is none
static QString getCurLockName();
/// Releases all locks on all profiles
/// DO NOT call unless all we're the only qTox instance
/// and we don't hold any lock yet.

View File

@ -19,6 +19,7 @@
#include "ui_mainwindow.h"
#include "src/widget/form/settingswidget.h"
#include "src/widget/maskablepixmapwidget.h"
#include "src/widget/form/setpassworddialog.h"
#include "src/misc/settings.h"
#include "src/widget/croppinglabel.h"
#include "src/widget/widget.h"
@ -26,6 +27,7 @@
#include "src/historykeeper.h"
#include "src/misc/style.h"
#include "src/profilelocker.h"
#include "src/profile.h"
#include <QLabel>
#include <QLineEdit>
#include <QGroupBox>
@ -35,17 +37,6 @@
#include <QFileDialog>
#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) :
QWidget{parent}, qr{nullptr}
{
@ -98,25 +89,14 @@ ProfileForm::ProfileForm(QWidget *parent) :
connect(bodyUI->toxIdLabel, SIGNAL(clicked()), this, SLOT(copyIdClicked()));
connect(toxId, SIGNAL(clicked()), this, SLOT(copyIdClicked()));
connect(core, &Core::idSet, this, &ProfileForm::setToxId);
connect(core, &Core::statusSet, this, &ProfileForm::onStatusSet);
connect(bodyUI->userName, SIGNAL(editingFinished()), this, SLOT(onUserNameEdited()));
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->exportButton, &QPushButton::clicked, this, &ProfileForm::onExportClicked);
connect(bodyUI->deleteButton, &QPushButton::clicked, this, &ProfileForm::onDeleteClicked);
connect(bodyUI->importButton, &QPushButton::clicked, this, &ProfileForm::onImportClicked);
connect(bodyUI->newButton, &QPushButton::clicked, this, &ProfileForm::onNewClicked);
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(bodyUI->logoutButton, &QPushButton::clicked, this, &ProfileForm::onLogoutClicked);
connect(bodyUI->deletePassButton, &QPushButton::clicked, this, &ProfileForm::onDeletePassClicked);
connect(bodyUI->changePassButton, &QPushButton::clicked, this, &ProfileForm::onChangePassClicked);
connect(core, &Core::usernameSet, this, [=](const QString& val) { bodyUI->userName->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->setQRData("tox:"+id);
bodyUI->qrCode->setPixmap(QPixmap::fromImage(qr->getImage()->scaledToWidth(150)));
refreshProfiles();
}
void ProfileForm::onAvatarClicked()
@ -250,58 +229,31 @@ void ProfileForm::onAvatarClicked()
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()
{
QString cur = bodyUI->profiles->currentText();
Nexus& nexus = Nexus::getInstance();
QString cur = nexus.getProfile()->getName();
QString title = tr("Rename \"%1\"", "renaming a profile").arg(cur);
do
{
QString name = QInputDialog::getText(this, title, title+":");
if (name.isEmpty()) break;
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;
}
} while (true);
}
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"),
QDir::home().filePath(current),
tr("Tox save file (*.tox)", "save dialog filter"));
@ -319,97 +271,30 @@ void ProfileForm::onExportClicked()
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"),
tr("Are you sure you want to delete this profile?","deletion confirmation text")))
{
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());
}
Nexus& nexus = Nexus::getInstance();
nexus.getProfile()->remove();
nexus.showLogin();
}
}
void ProfileForm::onImportClicked()
void ProfileForm::onLogoutClicked()
{
QString path = QFileDialog::getOpenFileName(0,
tr("Import profile", "import dialog title"),
QDir::homePath(),
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);
Nexus& nexus = Nexus::getInstance();
Settings::getInstance().save();
nexus.showLogin();
}
void ProfileForm::onStatusSet(Status)
{
refreshProfiles();
}
void ProfileForm::onNewClicked()
{
emit Widget::getInstance()->changeProfile(QString());
}
void ProfileForm::disableSwitching()
{
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()
void ProfileForm::onCopyQrClicked()
{
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"),
QDir::home().filePath(current),
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) &&
(qobject_cast<QComboBox*>(o) || qobject_cast<QAbstractSpinBox*>(o) ))
Profile* pro = Nexus::getProfile();
if (!pro->isEncrypted())
{
e->ignore();
return true;
GUI::showInfo(tr("Nothing to remove"), tr("Your profile does not have a password!"));
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);
}

View File

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

View File

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

View File

@ -48,51 +48,52 @@ void SetPasswordDialog::onPasswordEdit()
{
QString pswd = ui->passwordlineEdit->text();
if (pswd == ui->repasswordlineEdit->text() && pswd.length() > 0)
if (pswd.length() < 6)
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
ui->body->setText(body + tr("The password is too short"));
}
else if (pswd != ui->repasswordlineEdit->text())
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
ui->body->setText(body + tr("The password doesn't match."));
}
else
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
ui->body->setText(body);
}
else
ui->strengthBar->setValue(getPasswordStrength(pswd));
}
int SetPasswordDialog::getPasswordStrength(QString pass)
{
if (pass.size() < 6)
return 0;
double fscore = 0;
QHash<QChar, int> charCounts;
for (QChar c : pass)
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
ui->body->setText(body + tr("The passwords don't match."));
charCounts[c]++;
fscore += 5. / charCounts[c];
}
// Password strength calculator
// 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
int variations = -1;
variations += pass.contains(QRegExp("[0-9]", Qt::CaseSensitive, QRegExp::RegExp)) ? 1 : 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;
const double lengthFactor = reasonablePasswordLength / 8.0;
int pwlength = (int)(pswd.length() / lengthFactor);
if (pwlength > 5)
pwlength = 5;
int score = fscore;
score += variations * 10;
score -= 20;
score = std::min(score, 100);
score = std::max(score, 0);
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 numsymbols = (int)(pswd.count(symbRxp) / lengthFactor);
if (numsymbols > 3)
numsymbols = 3;
const QRegExp upperRxp("[A-Z]", Qt::CaseSensitive, QRegExp::RegExp);
int upper = (int)(pswd.count(upperRxp) / lengthFactor);
if (upper > 3)
upper = 3;
int pwstrength=((pwlength*10)-20) + (numeric*10) + (numsymbols*15) + (upper*10);
if (pwstrength < 0)
pwstrength = 0;
if (pwstrength > 100)
pwstrength = 100;
ui->strengthBar->setValue(pwstrength);
return score;
}
QString SetPasswordDialog::getPassword()

View File

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

View File

@ -20,6 +20,8 @@
#include "src/misc/smileypack.h"
#include "src/core/core.h"
#include "src/misc/style.h"
#include "src/nexus.h"
#include "src/profile.h"
#include <QMessageBox>
#include <QStyleFactory>
#include <QTime>
@ -349,9 +351,9 @@ void GeneralForm::onReconnectClicked()
{
if (Core::getInstance()->anyActiveCalls())
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
emit Widget::getInstance()->changeProfile(Settings::getInstance().getCurrentProfile());
Nexus::getProfile()->restartCore();
}
void GeneralForm::reloadSmiles()

View File

@ -31,15 +31,8 @@ PrivacyForm::PrivacyForm() :
bodyUI = new Ui::PrivacySettings;
bodyUI->setupUi(this);
bodyUI->encryptToxHLayout->addStretch();
bodyUI->encryptLogsHLayout->addStretch();
connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated()));
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->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam()));
connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit()));
@ -53,7 +46,6 @@ PrivacyForm::~PrivacyForm()
void PrivacyForm::onEnableLoggingUpdated()
{
Settings::getInstance().setEnableLogging(bodyUI->cbKeepHistory->isChecked());
bodyUI->cbEncryptHistory->setEnabled(bodyUI->cbKeepHistory->isChecked());
HistoryKeeper::resetInstance();
Widget::getInstance()->clearAllReceipts();
}
@ -63,205 +55,6 @@ void PrivacyForm::onTypingNotificationEnabledUpdated()
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()
{
QString newNospam = bodyUI->nospamLineEdit->text();
@ -277,11 +70,6 @@ void PrivacyForm::present()
bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam);
bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled());
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()

View File

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

View File

@ -37,7 +37,7 @@
<x>0</x>
<y>0</y>
<width>380</width>
<height>391</height>
<height>280</height>
</rect>
</property>
<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>
</property>
<property name="text">
<string>Send Typing Notifications</string>
<string>Send typing notifications</string>
</property>
</widget>
</item>
@ -58,77 +58,10 @@
Save format changes are possible, which may result in data loss.</string>
</property>
<property name="text">
<string>Keep chat history (mostly stable)</string>
<string>Keep chat history</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<widget class="QGroupBox" name="encryptionGroup">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Local file encryption</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="encryptStatement">
<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>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</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">
@ -138,9 +71,19 @@ 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>
<string>NoSpam</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>The NoSpam is part of your Tox ID, if you are getting spammed with friend requests, change the NoSpam.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@ -153,7 +96,7 @@ but new contacts need to know your new Tox ID to be able to add you.</string>
<item>
<widget class="QPushButton" name="randomNosapamButton">
<property name="text">
<string>Generate random nospam</string>
<string>Generate random NoSpam</string>
</property>
</widget>
</item>

View File

@ -35,6 +35,14 @@ GUI& GUI::getInstance()
// 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)
{
if (QThread::currentThread() == qApp->thread())
@ -192,6 +200,14 @@ QString GUI::passwordDialog(const QString& cancel, const QString& body)
// Private implementations
void GUI::_clearContacts()
{
#ifdef Q_OS_ANDROID
#else
Nexus::getDesktopGUI()->clearContactsList();
#endif
}
void GUI::_setEnabled(bool state)
{
#ifdef Q_OS_ANDROID

View File

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

167
src/widget/loginscreen.cpp Normal file
View File

@ -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 Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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