diff --git a/src/main.cpp b/src/main.cpp index 8b5a93d0f..5e769dbb5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,6 @@ */ #include "src/audio/audio.h" -#include "src/core/coreav.h" #include "src/ipc.h" #include "src/net/toxuri.h" #include "src/nexus.h" @@ -194,8 +193,8 @@ int main(int argc, char* argv[]) #endif qsrand(time(nullptr)); - Settings::getInstance(); - QString locale = Settings::getInstance().getTranslation(); + Settings& settings = Settings::getInstance(); + QString locale = settings.getTranslation(); Translator::translate(locale); // Process arguments @@ -215,15 +214,14 @@ int main(int argc, char* argv[]) QObject::tr("Starts new instance and opens the login screen."))); parser.process(*a); - uint32_t profileId = Settings::getInstance().getCurrentProfileId(); + uint32_t profileId = settings.getCurrentProfileId(); IPC ipc(profileId); if (!ipc.isAttached()) { qCritical() << "Can't init IPC"; return EXIT_FAILURE; } - QObject::connect(&Settings::getInstance(), &Settings::currentProfileIdChanged, &ipc, - &IPC::setProfileId); + QObject::connect(&settings, &Settings::currentProfileIdChanged, &ipc, &IPC::setProfileId); // For the auto-updater if (sodium_init() < 0) { @@ -232,7 +230,7 @@ int main(int argc, char* argv[]) } #ifdef LOG_TO_FILE - QString logFileDir = Settings::getInstance().getAppCacheDirPath(); + QString logFileDir = settings.getAppCacheDirPath(); QDir(logFileDir).mkpath("."); QString logfile = logFileDir + "qtox.log"; @@ -274,7 +272,7 @@ int main(int argc, char* argv[]) qDebug() << "commit: " << GIT_VERSION; QString profileName; - bool autoLogin = Settings::getInstance().getAutoLogin(); + bool autoLogin = settings.getAutoLogin(); uint32_t ipcDest = 0; bool doIpc = true; @@ -294,7 +292,7 @@ int main(int argc, char* argv[]) doIpc = false; autoLogin = false; } else { - profileName = Settings::getInstance().getCurrentProfile(); + profileName = settings.getCurrentProfile(); } if (parser.positionalArguments().size() == 0) { @@ -330,38 +328,27 @@ int main(int argc, char* argv[]) } } - Profile* profile = nullptr; + // TODO(sudden6): remove once we get rid of Nexus + Nexus& nexus = Nexus::getInstance(); + // TODO(kriby): Consider moving application initializing variables into a globalSettings object + // note: Because Settings is shouldering global settings as well as model specific ones it + // cannot be integrated into a central model object yet + nexus.setSettings(&settings); // Autologin + // TODO (kriby): Shift responsibility of linking views to model objects from nexus + // Further: generate view instances separately (loginScreen, mainGUI, audio) if (autoLogin && Profile::exists(profileName) && !Profile::isEncrypted(profileName)) { - profile = Profile::loadProfile(profileName); + Profile* profile = Profile::loadProfile(profileName); + settings.updateProfileData(profile); + nexus.bootstrapWithProfile(profile); } else { - LoginScreen loginScreen{profileName}; - loginScreen.exec(); - profile = loginScreen.getProfile(); - if (profile) { - profileName = profile->getName(); + int returnval = nexus.showLogin(profileName); + if (returnval != 0) { + return returnval; } } - if (!profile) { - return EXIT_FAILURE; - } - - Nexus::getInstance().setProfile(profile); - Settings& s = Settings::getInstance(); - s.setCurrentProfile(profileName); - - auto audio = Audio::makeAudio(s); - assert(audio != nullptr); - // TODO(sudden6): init CoreAV audio backend somewhere else so main doesn't depend on coreav.h - profile->getCore()->getAv()->setAudio(*audio); - - Nexus& nexus = Nexus::getInstance(); - // TODO(sudden6): remove once we get rid of Nexus - nexus.audio = audio.get(); - nexus.start(); - // Start to accept Inter-process communication ipc.registerEventHandler("uri", &toxURIEventHandler); ipc.registerEventHandler("save", &toxSaveEventHandler); diff --git a/src/nexus.cpp b/src/nexus.cpp index c88da24db..add239196 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #ifdef Q_OS_MAC #include @@ -67,7 +68,7 @@ Nexus::~Nexus() widget = nullptr; delete profile; profile = nullptr; - Settings::getInstance().saveGlobal(); + emit saveGlobal(); #ifdef Q_OS_MAC delete globalMenuBar; #endif @@ -148,7 +149,7 @@ void Nexus::start() /** * @brief Hides the main GUI, delete the profile, and shows the login screen */ -void Nexus::showLogin() +int Nexus::showLogin(const QString& profileName) { delete widget; widget = nullptr; @@ -156,26 +157,71 @@ void Nexus::showLogin() delete profile; profile = nullptr; - LoginScreen loginScreen; - loginScreen.exec(); + LoginScreen loginScreen{profileName}; + connectLoginScreen(loginScreen); - profile = loginScreen.getProfile(); + // TODO(kriby): Move core out of profile + // This is awkward because the core is in the profile + // The connection order ensures profile will be ready for bootstrap for now + connect(this, &Nexus::currentProfileChanged, this, &Nexus::bootstrapWithProfile); + int returnval = loginScreen.exec(); + disconnect(this, &Nexus::currentProfileChanged, this, &Nexus::bootstrapWithProfile); + return returnval; +} + +void Nexus::bootstrapWithProfile(Profile *p) +{ + // kriby: This is a hack until a proper controller is written + + profile = p; if (profile) { - Nexus::getInstance().setProfile(profile); - Settings::getInstance().setCurrentProfile(profile->getName()); - showMainGUI(); - } else { - qApp->quit(); + audioControl = std::unique_ptr(Audio::makeAudio(*settings)); + assert(audioControl != nullptr); + profile->getCore()->getAv()->setAudio(*audioControl); + start(); } } +void Nexus::setSettings(Settings* settings) +{ + if (this->settings) { + QObject::disconnect(this, &Nexus::currentProfileChanged, this->settings, + &Settings::updateProfileData); + QObject::disconnect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal); + } + this->settings = settings; + if (this->settings) { + QObject::connect(this, &Nexus::currentProfileChanged, this->settings, + &Settings::updateProfileData); + QObject::connect(this, &Nexus::saveGlobal, this->settings, &Settings::saveGlobal); + } +} + +void Nexus::connectLoginScreen(const LoginScreen& loginScreen) +{ + // TODO(kriby): Move connect sequences to a controller class object instead + + // Nexus -> LoginScreen + QObject::connect(this, &Nexus::profileLoaded, &loginScreen, &LoginScreen::onProfileLoaded); + QObject::connect(this, &Nexus::profileLoadFailed, &loginScreen, &LoginScreen::onProfileLoadFailed); + // LoginScreen -> Nexus + QObject::connect(&loginScreen, &LoginScreen::createNewProfile, this, &Nexus::onCreateNewProfile); + QObject::connect(&loginScreen, &LoginScreen::loadProfile, this, &Nexus::onLoadProfile); + // LoginScreen -> Settings + QObject::connect(&loginScreen, &LoginScreen::autoLoginChanged, settings, &Settings::onSetAutoLogin); + // Settings -> LoginScreen + QObject::connect(settings, &Settings::autoLoginChanged, &loginScreen, + &LoginScreen::onAutoLoginChanged); +} + void Nexus::showMainGUI() { + // TODO(kriby): Rewrite as view-model connect sequence only, add to a controller class object assert(profile); // Create GUI - widget = Widget::getInstance(audio); + widget = Widget::getInstance(audioControl.get()); // Start GUI widget->init(); @@ -270,14 +316,36 @@ Profile* Nexus::getProfile() } /** - * @brief Unload the current profile, if any, and replaces it. - * @param profile Profile to set. + * @brief Creates a new profile and replaces the current one. + * @param name New username + * @param pass New password */ -void Nexus::setProfile(Profile* profile) +void Nexus::onCreateNewProfile(const QString& name, const QString& pass) { - getInstance().profile = profile; - if (profile) - Settings::getInstance().loadPersonal(profile->getName(), profile->getPasskey()); + setProfile(Profile::createProfile(name, pass)); +} + +/** + * Loads an existing profile and replaces the current one. + */ +void Nexus::onLoadProfile(const QString& name, const QString& pass) +{ + setProfile(Profile::loadProfile(name, pass)); +} +/** + * Changes the loaded profile and notifies listeners. + * @param p + */ +void Nexus::setProfile(Profile* p) { + if (!p) { + emit profileLoadFailed(); + // Warnings are issued during respective createNew/load calls + return; + } else { + emit profileLoaded(); + } + + emit currentProfileChanged(p); } /** diff --git a/src/nexus.h b/src/nexus.h index 2ce624edf..43ec679a5 100644 --- a/src/nexus.h +++ b/src/nexus.h @@ -27,6 +27,8 @@ class Widget; class Profile; +class Settings; +class LoginScreen; class Core; #ifdef Q_OS_MAC @@ -44,16 +46,13 @@ class Nexus : public QObject public: void start(); void showMainGUI(); - + void setSettings(Settings* settings); static Nexus& getInstance(); static void destroyInstance(); static Core* getCore(); static Profile* getProfile(); - static void setProfile(Profile* profile); static Widget* getDesktopGUI(); -public slots: - void showLogin(); #ifdef Q_OS_MAC public: @@ -81,17 +80,29 @@ private: QSignalMapper* windowMapper; QActionGroup* windowActions = nullptr; #endif -public: - // TODO(sudden6): hack to pass the audio instance - IAudioControl* audio = nullptr; +signals: + void currentProfileChanged(Profile* Profile); + void profileLoaded(); + void profileLoadFailed(); + void saveGlobal(); + +public slots: + void onCreateNewProfile(const QString& name, const QString& pass); + void onLoadProfile(const QString& name, const QString& pass); + int showLogin(const QString& profileName = QString()); + void bootstrapWithProfile(Profile *p); private: explicit Nexus(QObject* parent = nullptr); + void connectLoginScreen(const LoginScreen& loginScreen); + void setProfile(Profile* p); ~Nexus(); private: Profile* profile; + Settings* settings; Widget* widget; + std::unique_ptr audioControl; }; #endif // NEXUS_H diff --git a/src/persistence/profile.cpp b/src/persistence/profile.cpp index b35fa9024..d75821373 100644 --- a/src/persistence/profile.cpp +++ b/src/persistence/profile.cpp @@ -105,9 +105,6 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile, , encrypted{this->passkey != nullptr} { Settings& s = Settings::getInstance(); - s.setCurrentProfile(name); - s.saveGlobal(); - s.loadPersonal(name, this->passkey.get()); initCore(toxsave, s, isNewProfile); const ToxId& selfId = core->getSelfId(); diff --git a/src/persistence/settings.cpp b/src/persistence/settings.cpp index df375137a..560f6224d 100644 --- a/src/persistence/settings.cpp +++ b/src/persistence/settings.cpp @@ -273,6 +273,19 @@ void Settings::loadGlobal() loaded = true; } +void Settings::updateProfileData(Profile *profile) +{ + QMutexLocker locker{&bigLock}; + + if (profile == nullptr) { + qWarning() << QString("Could not load new settings (profile change to nullptr)"); + return; + } + setCurrentProfile(profile->getName()); + saveGlobal(); + loadPersonal(profile->getName(), profile->getPasskey()); +} + void Settings::loadPersonal(QString profileName, const ToxEncrypt* passKey) { QMutexLocker locker{&bigLock}; @@ -1281,7 +1294,7 @@ void Settings::setCurrentProfile(const QString& profile) if (profile != currentProfile) { currentProfile = profile; currentProfileId = makeProfileId(currentProfile); - emit currentProfileChanged(currentProfile); + emit currentProfileIdChanged(currentProfileId); } } diff --git a/src/persistence/settings.h b/src/persistence/settings.h index 4028b7869..dfaaf5637 100644 --- a/src/persistence/settings.h +++ b/src/persistence/settings.h @@ -168,6 +168,8 @@ public: public slots: void saveGlobal(); void sync(); + void onSetAutoLogin(bool state); + void updateProfileData(Profile *profile); signals: // General @@ -190,7 +192,6 @@ signals: void toxmeBioChanged(const QString& bio); void toxmePrivChanged(bool priv); void toxmePassChanged(); - void currentProfileChanged(const QString& profile); void currentProfileIdChanged(quint32 id); void enableLoggingChanged(bool enabled); void autoAwayTimeChanged(int minutes); diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index 8c0a72479..acaf8ed7e 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -32,7 +32,7 @@ #include #include -LoginScreen::LoginScreen(QString initialProfile, QWidget* parent) +LoginScreen::LoginScreen(const QString& initialProfileName, QWidget* parent) : QDialog(parent) , ui(new Ui::LoginScreen) , quitShortcut{QKeySequence(Qt::CTRL + Qt::Key_Q), this} @@ -72,6 +72,15 @@ LoginScreen::~LoginScreen() delete ui; } +void LoginScreen::closeEvent(QCloseEvent* event) +{ + + // If we are in the bootstrap, returning -1 will give us something to exit with in main.cpp + this->setResult(-1); + // If we are in application exec, we can quit by closing it, instead. + qApp->quit(); +} + /** * @brief Resets the UI, clears all fields. */ @@ -103,14 +112,15 @@ void LoginScreen::reset(QString initialProfile) ui->loginPassword->setFocus(); } - ui->autoLoginCB->blockSignals(true); - ui->autoLoginCB->setChecked(Settings::getInstance().getAutoLogin()); - ui->autoLoginCB->blockSignals(false); +void LoginScreen::onProfileLoadFailed() { + QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Wrong password.")); + ui->loginPassword->setFocus(); + ui->loginPassword->selectAll(); } -Profile *LoginScreen::getProfile() const +void LoginScreen::onAutoLoginChanged(bool state) { - return profile; + ui->autoLoginCB->setChecked(state); } bool LoginScreen::event(QEvent* event) @@ -129,11 +139,6 @@ bool LoginScreen::event(QEvent* event) return QWidget::event(event); } -void LoginScreen::closeEvent(QCloseEvent*) -{ - emit closed(); -} - void LoginScreen::onNewProfilePageClicked() { ui->stackedWidget->setCurrentIndex(0); @@ -174,16 +179,7 @@ void LoginScreen::onCreateNewProfile() return; } - profile = Profile::createProfile(name, pass); - if (!profile) { - // Unknown error - QMessageBox::critical(this, tr("Couldn't create a new profile"), - tr("Unknown error: Couldn't create a new profile.\nIf you " - "encountered this error, please report it.")); - done(-1); - return; - } - done(0); + emit createNewProfile(name, pass); } void LoginScreen::onLoginUsernameSelected(const QString& name) @@ -227,20 +223,7 @@ void LoginScreen::onLogin() return; } - profile = Profile::loadProfile(name, pass); - if (!profile) { - if (!ProfileLocker::isLockable(name)) { - QMessageBox::critical(this, tr("Couldn't load this profile"), - tr("Profile already in use. Close other clients.")); - return; - } else { - QMessageBox::critical(this, tr("Couldn't load this profile"), tr("Wrong password.")); - ui->loginPassword->setFocus(); - ui->loginPassword->selectAll(); - return; - } - } - done(0); + emit loadProfile(name, pass); } void LoginScreen::onPasswordEdited() diff --git a/src/widget/loginscreen.h b/src/widget/loginscreen.h index 5b5050e23..4d1065527 100644 --- a/src/widget/loginscreen.h +++ b/src/widget/loginscreen.h @@ -36,20 +36,23 @@ class LoginScreen : public QDialog Q_OBJECT public: - LoginScreen(QString selectedProfile = QString(), QWidget* parent = nullptr); + LoginScreen(const QString& initialProfileName = QString(), QWidget* parent = nullptr); ~LoginScreen(); - void reset(QString selectedProfile = QString()); - Profile* getProfile() const; - bool event(QEvent* event) final override; signals: + void windowStateChanged(Qt::WindowStates states); - void closed(); + void createNewProfile(QString name, const QString& pass); + void loadProfile(QString name, const QString& pass); protected: virtual void closeEvent(QCloseEvent* event) final override; +public slots: + void onProfileLoaded(); + void onProfileLoadFailed(); + private slots: void onAutoLoginToggled(int state); void onLoginUsernameSelected(const QString& name); @@ -63,6 +66,7 @@ private slots: void onImportProfile(); private: + void reset(const QString& initialProfileName = QString()); void retranslateUi(); void showCapsIndicator(); void hideCapsIndicator(); @@ -71,7 +75,6 @@ private: private: Ui::LoginScreen* ui; QShortcut quitShortcut; - Profile* profile{nullptr}; }; #endif // LOGINSCREEN_H