From 3477226367ea44ce5e9ebddd8d29952706b749d0 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 4 Apr 2020 19:40:32 +0300 Subject: [PATCH 1/9] first moves to safe pasword storing, preparing the structure --- core/account.cpp | 13 +++++- core/account.h | 11 ++++- core/squawk.cpp | 88 ++++++++++++++++++++++++++---------- core/squawk.h | 26 +++++++++-- main.cpp | 2 +- shared/enums.h | 37 ++++++++------- shared/global.cpp | 94 ++++++++++++++------------------------- shared/global.h | 16 +++++++ shared/message.h | 2 + translations/squawk.ru.ts | 20 +++++++++ ui/models/account.cpp | 28 +++++++++++- ui/models/account.h | 7 ++- ui/models/participant.cpp | 29 +++--------- ui/models/participant.h | 1 + ui/squawk.cpp | 4 +- ui/squawk.h | 2 +- ui/widgets/account.cpp | 15 +++++-- ui/widgets/account.h | 4 +- ui/widgets/account.ui | 18 ++++++-- ui/widgets/accounts.cpp | 14 +++--- ui/widgets/accounts.h | 10 ++--- 21 files changed, 288 insertions(+), 153 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index 37a8c16..ec92e96 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -53,7 +53,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& avatarType(), ownVCardRequestInProgress(false), network(p_net), - pendingStateMessages() + pendingStateMessages(), + passwordType(Shared::AccountPassword::plain) { config.setUser(p_login); config.setDomain(p_server); @@ -266,6 +267,11 @@ QString Core::Account::getServer() const return config.domain(); } +Shared::AccountPassword Core::Account::getPasswordType() const +{ + return passwordType; +} + void Core::Account::onRosterReceived() { vm->requestClientVCard(); //TODO need to make sure server actually supports vCards @@ -296,6 +302,11 @@ void Core::Account::onRosterItemAdded(const QString& bareJid) } } +void Core::Account::setPasswordType(Shared::AccountPassword pt) +{ + passwordType = pt; +} + void Core::Account::onRosterItemChanged(const QString& bareJid) { std::map::const_iterator itr = contacts.find(bareJid); diff --git a/core/account.h b/core/account.h index 1688fc9..386835c 100644 --- a/core/account.h +++ b/core/account.h @@ -55,7 +55,13 @@ class Account : public QObject { Q_OBJECT public: - Account(const QString& p_login, const QString& p_server, const QString& p_password, const QString& p_name, NetworkAccess* p_net, QObject* parent = 0); + Account( + const QString& p_login, + const QString& p_server, + const QString& p_password, + const QString& p_name, + NetworkAccess* p_net, + QObject* parent = 0); ~Account(); void connect(); @@ -70,6 +76,7 @@ public: QString getResource() const; QString getAvatarPath() const; Shared::Availability getAvailability() const; + Shared::AccountPassword getPasswordType() const; void setName(const QString& p_name); void setLogin(const QString& p_login); @@ -77,6 +84,7 @@ public: void setPassword(const QString& p_password); void setResource(const QString& p_resource); void setAvailability(Shared::Availability avail); + void setPasswordType(Shared::AccountPassword pt); QString getFullJid() const; void sendMessage(Shared::Message data); void sendMessage(const Shared::Message& data, const QString& path); @@ -158,6 +166,7 @@ private: bool ownVCardRequestInProgress; NetworkAccess* network; std::map pendingStateMessages; + Shared::AccountPassword passwordType; private slots: void onClientConnected(); diff --git a/core/squawk.cpp b/core/squawk.cpp index 387c685..e8dd1ae 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -26,7 +26,8 @@ Core::Squawk::Squawk(QObject* parent): QObject(parent), accounts(), amap(), - network() + network(), + waitingForAccounts(0) { connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse); connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress); @@ -72,21 +73,7 @@ void Core::Squawk::start() { qDebug("Starting squawk core.."); - QSettings settings; - settings.beginGroup("core"); - int size = settings.beginReadArray("accounts"); - for (int i = 0; i < size; ++i) { - settings.setArrayIndex(i); - addAccount( - settings.value("login").toString(), - settings.value("server").toString(), - settings.value("password").toString(), - settings.value("name").toString(), - settings.value("resource").toString() - ); - } - settings.endArray(); - settings.endGroup(); + readSettings(); network.start(); } @@ -98,10 +85,17 @@ void Core::Squawk::newAccountRequest(const QMap& map) QString password = map.value("password").toString(); QString resource = map.value("resource").toString(); - addAccount(login, server, password, name, resource); + addAccount(login, server, password, name, resource, Shared::AccountPassword::plain); } -void Core::Squawk::addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource) +void Core::Squawk::addAccount( + const QString& login, + const QString& server, + const QString& password, + const QString& name, + const QString& resource, + Shared::AccountPassword passwordType +) { QSettings settings; unsigned int reconnects = settings.value("reconnects", 2).toUInt(); @@ -109,6 +103,7 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const Account* acc = new Account(login, server, password, name, &network); acc->setResource(resource); acc->setReconnectTimes(reconnects); + acc->setPasswordType(passwordType); accounts.push_back(acc); amap.insert(std::make_pair(name, acc)); @@ -119,8 +114,10 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact); connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup); connect(acc, &Account::removeGroup, this, &Squawk::onAccountRemoveGroup); - connect(acc, qOverload(&Account::removeContact), this, qOverload(&Squawk::onAccountRemoveContact)); - connect(acc, qOverload(&Account::removeContact), this, qOverload(&Squawk::onAccountRemoveContact)); + connect(acc, qOverload(&Account::removeContact), + this, qOverload(&Squawk::onAccountRemoveContact)); + connect(acc, qOverload(&Account::removeContact), + this, qOverload(&Squawk::onAccountRemoveContact)); connect(acc, &Account::changeContact, this, &Squawk::onAccountChangeContact); connect(acc, &Account::addPresence, this, &Squawk::onAccountAddPresence); connect(acc, &Account::removePresence, this, &Squawk::onAccountRemovePresence); @@ -149,7 +146,8 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const {"state", QVariant::fromValue(Shared::ConnectionState::disconnected)}, {"offline", QVariant::fromValue(Shared::Availability::offline)}, {"error", ""}, - {"avatarPath", acc->getAvatarPath()} + {"avatarPath", acc->getAvatarPath()}, + {"passwordType", QVariant::fromValue(passwordType)} }; emit newAccount(map); @@ -486,8 +484,6 @@ void Core::Squawk::onAccountRemoveRoomPresence(const QString& jid, const QString emit removeRoomParticipant(acc->getName(), jid, nick); } - - void Core::Squawk::onAccountChangeMessage(const QString& jid, const QString& id, const QMap& data) { Account* acc = static_cast(sender()); @@ -574,3 +570,49 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card itr->second->uploadVCard(card); } +void Core::Squawk::readSettings() +{ + QSettings settings; + settings.beginGroup("core"); + int size = settings.beginReadArray("accounts"); + waitingForAccounts = size; + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + parseAccount( + settings.value("login").toString(), + settings.value("server").toString(), + settings.value("password", "").toString(), + settings.value("name").toString(), + settings.value("resource").toString(), + Shared::Global::fromInt(settings.value("passwordType", static_cast(Shared::AccountPassword::plain)).toInt()) + ); + } + settings.endArray(); + settings.endGroup(); +} + +void Core::Squawk::accountReady() +{ + --waitingForAccounts; + + if (waitingForAccounts == 0) { + emit ready(); + } +} + +void Core::Squawk::parseAccount( + const QString& login, + const QString& server, + const QString& password, + const QString& name, + const QString& resource, + Shared::AccountPassword passwordType +) +{ + switch (passwordType) { + case Shared::AccountPassword::plain: + addAccount(login, server, password, name, resource, passwordType); + accountReady(); + break; + } +} diff --git a/core/squawk.h b/core/squawk.h index 29e5b8c..6d5f7d9 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -45,6 +45,7 @@ public: signals: void quit(); + void ready(); void newAccount(const QMap&); void changeAccount(const QString& account, const QMap& data); void removeAccount(const QString& account); @@ -109,11 +110,18 @@ private: AccountsMap amap; Shared::Availability state; NetworkAccess network; - -private: - void addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource); + uint8_t waitingForAccounts; private slots: + void addAccount( + const QString& login, + const QString& server, + const QString& password, + const QString& name, + const QString& resource, + Shared::AccountPassword passwordType + ); + void onAccountConnectionStateChanged(Shared::ConnectionState state); void onAccountAvailabilityChanged(Shared::Availability state); void onAccountChanged(const QMap& data); @@ -135,6 +143,18 @@ private slots: void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap& data); void onAccountRemoveRoomPresence(const QString& jid, const QString& nick); void onAccountChangeMessage(const QString& jid, const QString& id, const QMap& data); + +private: + void readSettings(); + void accountReady(); + void parseAccount( + const QString& login, + const QString& server, + const QString& password, + const QString& name, + const QString& resource, + Shared::AccountPassword passwordType + ); }; } diff --git a/main.cpp b/main.cpp index 25de512..eeb7138 100644 --- a/main.cpp +++ b/main.cpp @@ -146,9 +146,9 @@ int main(int argc, char *argv[]) QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress); QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError); QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); + QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings); coreThread->start(); - w.readSettings(); int result = app.exec(); diff --git a/shared/enums.h b/shared/enums.h index 158695c..bf55377 100644 --- a/shared/enums.h +++ b/shared/enums.h @@ -35,8 +35,8 @@ enum class ConnectionState { }; Q_ENUM_NS(ConnectionState) static const std::deque connectionStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"}; -static const ConnectionState connectionStateHighest = ConnectionState::error; -static const ConnectionState connectionStateLowest = ConnectionState::disconnected; +static const ConnectionState ConnectionStateHighest = ConnectionState::error; +static const ConnectionState ConnectionStateLowest = ConnectionState::disconnected; enum class Availability { online, @@ -48,8 +48,8 @@ enum class Availability { offline }; Q_ENUM_NS(Availability) -static const Availability availabilityHighest = Availability::offline; -static const Availability availabilityLowest = Availability::online; +static const Availability AvailabilityHighest = Availability::offline; +static const Availability AvailabilityLowest = Availability::online; static const std::deque availabilityThemeIcons = { "user-online", "user-away", @@ -59,7 +59,6 @@ static const std::deque availabilityThemeIcons = { "user-invisible", "user-offline" }; -static const std::deque availabilityNames = {"Online", "Away", "Absent", "Busy", "Chatty", "Invisible", "Offline"}; enum class SubscriptionState { none, @@ -69,10 +68,9 @@ enum class SubscriptionState { unknown }; Q_ENUM_NS(SubscriptionState) -static const SubscriptionState subscriptionStateHighest = SubscriptionState::unknown; -static const SubscriptionState subscriptionStateLowest = SubscriptionState::none; +static const SubscriptionState SubscriptionStateHighest = SubscriptionState::unknown; +static const SubscriptionState SubscriptionStateLowest = SubscriptionState::none; static const std::deque subscriptionStateThemeIcons = {"edit-none", "arrow-down-double", "arrow-up-double", "dialog-ok", "question"}; -static const std::deque subscriptionStateNames = {"None", "From", "To", "Both", "Unknown"}; enum class Affiliation { unspecified, @@ -83,9 +81,8 @@ enum class Affiliation { owner }; Q_ENUM_NS(Affiliation) -static const Affiliation affiliationHighest = Affiliation::owner; -static const Affiliation affiliationLowest = Affiliation::unspecified; -static const std::deque affiliationNames = {"Unspecified", "Outcast", "Nobody", "Member", "Admin", "Owner"}; +static const Affiliation AffiliationHighest = Affiliation::owner; +static const Affiliation AffiliationLowest = Affiliation::unspecified; enum class Role { unspecified, @@ -95,9 +92,8 @@ enum class Role { moderator }; Q_ENUM_NS(Role) -static const Role roleHighest = Role::moderator; -static const Role roleLowest = Role::unspecified; -static const std::deque roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"}; +static const Role RoleHighest = Role::moderator; +static const Role RoleLowest = Role::unspecified; enum class Avatar { empty, @@ -105,10 +101,21 @@ enum class Avatar { valid }; Q_ENUM_NS(Avatar) +static const Avatar AvatarHighest = Avatar::valid; +static const Avatar AvatarLowest = Avatar::empty; -static const std::deque messageStateNames = {"Pending", "Sent", "Delivered", "Error"}; static const std::deque messageStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"}; +enum class AccountPassword { + plain, + jammed, + kwallet, + alwaysAsk +}; +Q_ENUM_NS(AccountPassword) +static const AccountPassword AccountPasswordHighest = AccountPassword::alwaysAsk; +static const AccountPassword AccountPasswordLowest = AccountPassword::plain; + } #endif // SHARED_ENUMS_H diff --git a/shared/global.cpp b/shared/global.cpp index 4d3399a..81bee3f 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -65,6 +65,12 @@ Shared::Global::Global(): tr("Sent"), tr("Delivered"), tr("Error") + }), + accountPassword({ + tr("Plain"), + tr("Jammed"), + tr("KWallet"), + tr("Always Ask") }) { if (instance != 0) { @@ -81,90 +87,56 @@ Shared::Global * Shared::Global::getInstance() QString Shared::Global::getName(Message::State rl) { - return instance->messageState[int(rl)]; + return instance->messageState[static_cast(rl)]; } QString Shared::Global::getName(Shared::Affiliation af) { - return instance->affiliation[int(af)]; + return instance->affiliation[static_cast(af)]; } QString Shared::Global::getName(Shared::Availability av) { - return instance->availability[int(av)]; + return instance->availability[static_cast(av)]; } QString Shared::Global::getName(Shared::ConnectionState cs) { - return instance->connectionState[int(cs)]; + return instance->connectionState[static_cast(cs)]; } QString Shared::Global::getName(Shared::Role rl) { - return instance->role[int(rl)]; + return instance->role[static_cast(rl)]; } QString Shared::Global::getName(Shared::SubscriptionState ss) { - return instance->subscriptionState[int(ss)]; + return instance->subscriptionState[static_cast(ss)]; } -template<> -Shared::Availability Shared::Global::fromInt(int src) +QString Shared::Global::getName(Shared::AccountPassword ap) { - if (src < static_cast(Shared::availabilityLowest) && src > static_cast(Shared::availabilityHighest)) { - qDebug("An attempt to set invalid availability to Squawk core, skipping"); - } - - return static_cast(src); + return instance->accountPassword[static_cast(ap)]; } -template<> -Shared::Availability Shared::Global::fromInt(unsigned int src) -{ - if (src < static_cast(Shared::availabilityLowest) && src > static_cast(Shared::availabilityHighest)) { - qDebug("An attempt to set invalid availability to Squawk core, skipping"); - } - - return static_cast(src); -} +#define FROM_INT_INPL(Enum) \ +template<> \ +Enum Shared::Global::fromInt(int src) \ +{ \ + if (src < static_cast(Enum##Lowest) && src > static_cast(Enum##Highest)) { \ + throw EnumOutOfRange(#Enum); \ + } \ + return static_cast(src); \ +} \ +template<> \ +Enum Shared::Global::fromInt(unsigned int src) {return fromInt(static_cast(src));} -template<> -Shared::ConnectionState Shared::Global::fromInt(int src) -{ - if (src < static_cast(Shared::connectionStateLowest) && src > static_cast(Shared::connectionStateHighest)) { - qDebug("An attempt to set invalid availability to Squawk core, skipping"); - } - - return static_cast(src); -} - -template<> -Shared::ConnectionState Shared::Global::fromInt(unsigned int src) -{ - if (src < static_cast(Shared::connectionStateLowest) && src > static_cast(Shared::connectionStateHighest)) { - qDebug("An attempt to set invalid availability to Squawk core, skipping"); - } - - return static_cast(src); -} - -template<> -Shared::SubscriptionState Shared::Global::fromInt(int src) -{ - if (src < static_cast(Shared::subscriptionStateLowest) && src > static_cast(Shared::subscriptionStateHighest)) { - qDebug("An attempt to set invalid availability to Squawk core, skipping"); - } - - return static_cast(src); -} - -template<> -Shared::SubscriptionState Shared::Global::fromInt(unsigned int src) -{ - if (src < static_cast(Shared::subscriptionStateLowest) && src > static_cast(Shared::subscriptionStateHighest)) { - qDebug("An attempt to set invalid availability to Squawk core, skipping"); - } - - return static_cast(src); -} +FROM_INT_INPL(Shared::Message::State) +FROM_INT_INPL(Shared::Affiliation) +FROM_INT_INPL(Shared::ConnectionState) +FROM_INT_INPL(Shared::Role) +FROM_INT_INPL(Shared::SubscriptionState) +FROM_INT_INPL(Shared::AccountPassword) +FROM_INT_INPL(Shared::Avatar) +FROM_INT_INPL(Shared::Availability) diff --git a/shared/global.h b/shared/global.h index ef61611..3ea6147 100644 --- a/shared/global.h +++ b/shared/global.h @@ -21,6 +21,7 @@ #include "enums.h" #include "message.h" +#include "exception.h" #include @@ -42,6 +43,7 @@ namespace Shared { static QString getName(Affiliation af); static QString getName(Role rl); static QString getName(Message::State rl); + static QString getName(AccountPassword ap); const std::deque availability; const std::deque connectionState; @@ -49,6 +51,7 @@ namespace Shared { const std::deque affiliation; const std::deque role; const std::deque messageState; + const std::deque accountPassword; template static T fromInt(int src); @@ -56,6 +59,19 @@ namespace Shared { template static T fromInt(unsigned int src); + class EnumOutOfRange: + public Utils::Exception + { + public: + EnumOutOfRange(const std::string& p_name):Exception(), name(p_name) {} + + std::string getMessage() const{ + return "An attempt to get enum " + name + " from integer out of range of that enum"; + } + private: + std::string name; + }; + private: static Global* instance; }; diff --git a/shared/message.h b/shared/message.h index 98ef206..4a0d661 100644 --- a/shared/message.h +++ b/shared/message.h @@ -46,6 +46,8 @@ public: delivered, error }; + static const State StateHighest = State::error; + static const State StateLowest = State::pending; Message(Type p_type); Message(); diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts index 37686fb..8a733f2 100644 --- a/translations/squawk.ru.ts +++ b/translations/squawk.ru.ts @@ -65,6 +65,10 @@ Ресурс по умолчанию QXmpp + + Password storage + + Accounts @@ -270,6 +274,22 @@ p, li { white-space: pre-wrap; } Delivered Доставлено + + Plain + + + + Jammed + + + + KWallet + + + + Always Ask + + JoinConference diff --git a/ui/models/account.cpp b/ui/models/account.cpp index 8d9774e..c581439 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -28,7 +28,8 @@ Models::Account::Account(const QMap& data, Models::Item* pare error(data.value("error").toString()), avatarPath(data.value("avatarPath").toString()), state(Shared::ConnectionState::disconnected), - availability(Shared::Availability::offline) + availability(Shared::Availability::offline), + passwordType(Shared::AccountPassword::plain) { QMap::const_iterator sItr = data.find("state"); if (sItr != data.end()) { @@ -155,6 +156,8 @@ QVariant Models::Account::data(int column) const return resource; case 8: return avatarPath; + case 9: + return Shared::Global::getName(passwordType); default: return QVariant(); } @@ -162,7 +165,7 @@ QVariant Models::Account::data(int column) const int Models::Account::columnCount() const { - return 9; + return 10; } void Models::Account::update(const QString& field, const QVariant& value) @@ -185,6 +188,8 @@ void Models::Account::update(const QString& field, const QVariant& value) setError(value.toString()); } else if (field == "avatarPath") { setAvatarPath(value.toString()); + } else if (field == "passwordType") { + setPasswordType(value.toUInt()); } } @@ -240,3 +245,22 @@ QString Models::Account::getFullJid() const { return getBareJid() + "/" + resource; } + +Shared::AccountPassword Models::Account::getPasswordType() const +{ + return passwordType; +} + +void Models::Account::setPasswordType(Shared::AccountPassword pt) +{ + if (passwordType != pt) { + passwordType = pt; + changed(9); + } +} + +void Models::Account::setPasswordType(unsigned int pt) +{ + setPasswordType(Shared::Global::fromInt(pt)); +} + diff --git a/ui/models/account.h b/ui/models/account.h index 428b049..d2bb79f 100644 --- a/ui/models/account.h +++ b/ui/models/account.h @@ -19,11 +19,11 @@ #ifndef MODELS_ACCOUNT_H #define MODELS_ACCOUNT_H +#include "item.h" #include "shared/enums.h" #include "shared/utils.h" #include "shared/icons.h" #include "shared/global.h" -#include "item.h" #include #include @@ -60,6 +60,10 @@ namespace Models { void setAvailability(unsigned int p_avail); Shared::Availability getAvailability() const; + void setPasswordType(Shared::AccountPassword pt); + void setPasswordType(unsigned int pt); + Shared::AccountPassword getPasswordType() const; + QIcon getStatusIcon(bool big = false) const; QVariant data(int column) const override; @@ -79,6 +83,7 @@ namespace Models { QString avatarPath; Shared::ConnectionState state; Shared::Availability availability; + Shared::AccountPassword passwordType; protected slots: void toOfflineState() override; diff --git a/ui/models/participant.cpp b/ui/models/participant.cpp index 3939888..dc42c07 100644 --- a/ui/models/participant.cpp +++ b/ui/models/participant.cpp @@ -58,11 +58,11 @@ QVariant Models::Participant::data(int column) const { switch (column) { case 4: - return static_cast(affiliation); + return QVariant::fromValue(affiliation); case 5: - return static_cast(role); + return QVariant::fromValue(role); case 6: - return static_cast(getAvatarState()); + return QVariant::fromValue(getAvatarState()); case 7: return getAvatarPath(); default: @@ -100,12 +100,7 @@ void Models::Participant::setAffiliation(Shared::Affiliation p_aff) void Models::Participant::setAffiliation(unsigned int aff) { - if (aff <= static_cast(Shared::affiliationHighest)) { - Shared::Affiliation affil = static_cast(aff); - setAffiliation(affil); - } else { - qDebug() << "An attempt to set wrong affiliation" << aff << "to the room participant" << name; - } + setAffiliation(Shared::Global::fromInt(aff)); } Shared::Role Models::Participant::getRole() const @@ -123,12 +118,7 @@ void Models::Participant::setRole(Shared::Role p_role) void Models::Participant::setRole(unsigned int p_role) { - if (p_role <= static_cast(Shared::roleHighest)) { - Shared::Role r = static_cast(p_role); - setRole(r); - } else { - qDebug() << "An attempt to set wrong role" << p_role << "to the room participant" << name; - } + setRole(Shared::Global::fromInt(p_role)); } QString Models::Participant::getAvatarPath() const @@ -158,11 +148,4 @@ void Models::Participant::setAvatarState(Shared::Avatar p_state) } void Models::Participant::setAvatarState(unsigned int p_state) -{ - if (p_state <= static_cast(Shared::Avatar::valid)) { - Shared::Avatar state = static_cast(p_state); - setAvatarState(state); - } else { - qDebug() << "An attempt to set invalid avatar state" << p_state << "to the room participant" << name << ", skipping"; - } -} +{setAvatarState(Shared::Global::fromInt(p_state));} diff --git a/ui/models/participant.h b/ui/models/participant.h index a93cb6d..6666d84 100644 --- a/ui/models/participant.h +++ b/ui/models/participant.h @@ -20,6 +20,7 @@ #define MODELS_PARTICIPANT_H #include "abstractparticipant.h" +#include "shared/global.h" namespace Models { diff --git a/ui/squawk.cpp b/ui/squawk.cpp index ad002c3..6822dd2 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -41,7 +41,7 @@ Squawk::Squawk(QWidget *parent) : m_ui->roster->header()->setStretchLastSection(false); m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch); - for (int i = static_cast(Shared::availabilityLowest); i < static_cast(Shared::availabilityHighest) + 1; ++i) { + for (int i = static_cast(Shared::AvailabilityLowest); i < static_cast(Shared::AvailabilityHighest) + 1; ++i) { Shared::Availability av = static_cast(i); m_ui->comboBox->addItem(Shared::availabilityIcon(av), Shared::Global::getName(av)); } @@ -71,7 +71,7 @@ Squawk::~Squawk() { void Squawk::onAccounts() { if (accounts == 0) { - accounts = new Accounts(rosterModel.accountsModel, this); + accounts = new Accounts(rosterModel.accountsModel); accounts->setAttribute(Qt::WA_DeleteOnClose); connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed); connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest); diff --git a/ui/squawk.h b/ui/squawk.h index adfff1d..64459ab 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -52,7 +52,6 @@ public: explicit Squawk(QWidget *parent = nullptr); ~Squawk() override; - void readSettings(); void writeSettings(); signals: @@ -82,6 +81,7 @@ signals: void uploadVCard(const QString& account, const Shared::VCard& card); public slots: + void readSettings(); void newAccount(const QMap& account); void changeAccount(const QString& account, const QMap& data); void removeAccount(const QString& account); diff --git a/ui/widgets/account.cpp b/ui/widgets/account.cpp index d42cca8..d417d4f 100644 --- a/ui/widgets/account.cpp +++ b/ui/widgets/account.cpp @@ -19,10 +19,17 @@ #include "account.h" #include "ui_account.h" -Account::Account() - : m_ui ( new Ui::Account ) +Account::Account(): + QDialog(), + m_ui(new Ui::Account) { - m_ui->setupUi ( this ); + m_ui->setupUi (this); + + for (int i = static_cast(Shared::AccountPasswordLowest); i < static_cast(Shared::AccountPasswordHighest) + 1; ++i) { + Shared::AccountPassword ap = static_cast(i); + m_ui->passwordType->addItem(Shared::Global::getName(ap)); + } + m_ui->passwordType->setCurrentIndex(static_cast(Shared::AccountPassword::plain)); } Account::~Account() @@ -37,6 +44,7 @@ QMap Account::value() const map["server"] = m_ui->server->text(); map["name"] = m_ui->name->text(); map["resource"] = m_ui->resource->text(); + map["passwordType"] = m_ui->passwordType->currentIndex(); return map; } @@ -53,4 +61,5 @@ void Account::setData(const QMap& data) m_ui->server->setText(data.value("server").toString()); m_ui->name->setText(data.value("name").toString()); m_ui->resource->setText(data.value("resource").toString()); + m_ui->passwordType->setCurrentIndex(data.value("passwordType").toInt()); } diff --git a/ui/widgets/account.h b/ui/widgets/account.h index 2f41430..9732224 100644 --- a/ui/widgets/account.h +++ b/ui/widgets/account.h @@ -19,12 +19,14 @@ #ifndef ACCOUNT_H #define ACCOUNT_H -#include #include +#include #include #include #include +#include "shared/global.h" + namespace Ui { class Account; diff --git a/ui/widgets/account.ui b/ui/widgets/account.ui index bfd0926..28cb389 100644 --- a/ui/widgets/account.ui +++ b/ui/widgets/account.ui @@ -6,8 +6,8 @@ 0 0 - 395 - 272 + 438 + 342 @@ -114,14 +114,14 @@ - + Resource - + A resource name like "Home" or "Work" @@ -131,6 +131,16 @@ + + + + Password storage + + + + + + diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts.cpp index e6c3da1..626915e 100644 --- a/ui/widgets/accounts.cpp +++ b/ui/widgets/accounts.cpp @@ -22,6 +22,7 @@ #include Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) : + QWidget(parent), m_ui(new Ui::Accounts), model(p_model), editing(false), @@ -40,7 +41,7 @@ Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) : Accounts::~Accounts() = default; -void Accounts::onAddButton(bool clicked) +void Accounts::onAddButton() { Account* acc = new Account(); connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted); @@ -70,7 +71,7 @@ void Accounts::onAccountRejected() editing = false; } -void Accounts::onEditButton(bool clicked) +void Accounts::onEditButton() { Account* acc = new Account(); @@ -80,7 +81,8 @@ void Accounts::onEditButton(bool clicked) {"password", mAcc->getPassword()}, {"server", mAcc->getServer()}, {"name", mAcc->getName()}, - {"resource", mAcc->getResource()} + {"resource", mAcc->getResource()}, + {"passwordType", QVariant::fromValue(mAcc->getPasswordType())} }); acc->lockId(); connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted); @@ -89,7 +91,7 @@ void Accounts::onEditButton(bool clicked) acc->exec(); } -void Accounts::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +void Accounts::onSelectionChanged() { int selectionSize = m_ui->tableView->selectionModel()->selection().size(); if (selectionSize == 0) { @@ -131,7 +133,7 @@ void Accounts::updateConnectButton() } } -void Accounts::onConnectButton(bool clicked) +void Accounts::onConnectButton() { QItemSelectionModel* sm = m_ui->tableView->selectionModel(); int selectionSize = sm->selection().size(); @@ -145,7 +147,7 @@ void Accounts::onConnectButton(bool clicked) } } -void Accounts::onDeleteButton(bool clicked) +void Accounts::onDeleteButton() { QItemSelectionModel* sm = m_ui->tableView->selectionModel(); int selectionSize = sm->selection().size(); diff --git a/ui/widgets/accounts.h b/ui/widgets/accounts.h index 31dc9ee..9fd0b57 100644 --- a/ui/widgets/accounts.h +++ b/ui/widgets/accounts.h @@ -46,13 +46,13 @@ signals: void removeAccount(const QString&); private slots: - void onAddButton(bool clicked = 0); - void onEditButton(bool clicked = 0); - void onConnectButton(bool clicked = 0); - void onDeleteButton(bool clicked = 0); + void onAddButton(); + void onEditButton(); + void onConnectButton(); + void onDeleteButton(); void onAccountAccepted(); void onAccountRejected(); - void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void onSelectionChanged(); void updateConnectButton(); private: From 95f0d4008a12842fc2651f56575c782b12f51806 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 5 Apr 2020 16:25:27 +0300 Subject: [PATCH 2/9] minor fix about updating muc avatars --- core/account.cpp | 6 +++--- core/conference.cpp | 50 +++++++++++++++++++++++++++++++++------------ core/rosteritem.cpp | 1 - 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index ec92e96..2f8238a 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1374,13 +1374,13 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS bool hasAvatar = conf->readAvatarInfo(info); if (hasAvatar) { if (info.autogenerated) { - cData.insert("avatarState", static_cast(Shared::Avatar::valid)); + cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated)); } else { - cData.insert("avatarState", static_cast(Shared::Avatar::autocreated)); + cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid)); } cData.insert("avatarPath", conf->avatarPath() + "." + info.type); } else { - cData.insert("avatarState", static_cast(Shared::Avatar::empty)); + cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty)); cData.insert("avatarPath", ""); requestVCard(jid); } diff --git a/core/conference.cpp b/core/conference.cpp index f0b3b7d..d745227 100644 --- a/core/conference.cpp +++ b/core/conference.cpp @@ -134,17 +134,20 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) { QStringList comps = p_name.split("/"); QString resource = comps.back(); + QXmppPresence pres = room->participantPresence(p_name); + QXmppMucItem mi = pres.mucItem(); if (resource == jid) { - qDebug() << "Room" << jid << "is reporting of adding itself to the list participants. Not sure what to do with that yet, skipping"; - } else { - QXmppPresence pres = room->participantPresence(p_name); + resource = ""; + } + + Archive::AvatarInfo info; + bool hasAvatar = readAvatarInfo(info, resource); + + if (resource.size() > 0) { QDateTime lastInteraction = pres.lastUserInteraction(); if (!lastInteraction.isValid()) { lastInteraction = QDateTime::currentDateTimeUtc(); } - QXmppMucItem mi = pres.mucItem(); - Archive::AvatarInfo info; - bool hasAvatar = readAvatarInfo(info, resource); QMap cData = { {"lastActivity", lastInteraction}, @@ -169,22 +172,43 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) emit addParticipant(resource, cData); } + + switch (pres.vCardUpdateType()) { + case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo + break; + case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here + break; + case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any + if (!hasAvatar || !info.autogenerated) { + setAutoGeneratedAvatar(resource); + } + } + break; + case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load + if (hasAvatar) { + if (info.autogenerated || info.hash != pres.photoHash()) { + emit requestVCard(p_name); + } + } else { + emit requestVCard(p_name); + } + break; + } + } } void Core::Conference::onRoomParticipantChanged(const QString& p_name) { QStringList comps = p_name.split("/"); QString resource = comps.back(); - if (resource == jid) { - qDebug() << "Room" << jid << "is reporting of changing his own presence. Not sure what to do with that yet, skipping"; - } else { - QXmppPresence pres = room->participantPresence(p_name); + QXmppPresence pres = room->participantPresence(p_name); + QXmppMucItem mi = pres.mucItem(); + handlePresence(pres); + if (resource != jid) { QDateTime lastInteraction = pres.lastUserInteraction(); if (!lastInteraction.isValid()) { lastInteraction = QDateTime::currentDateTimeUtc(); } - QXmppMucItem mi = pres.mucItem(); - handlePresence(pres); emit changeParticipant(resource, { {"lastActivity", lastInteraction}, @@ -265,7 +289,7 @@ bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) if (result && resource.size() != 0) { emit changeParticipant(resource, { {"avatarState", static_cast(Shared::Avatar::autocreated)}, - {"availability", avatarPath(resource) + ".png"} + {"avatarPath", avatarPath(resource) + ".png"} }); } diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index 59aa4a7..59b84f8 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -501,7 +501,6 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co path = avatarPath(resource) + ".png"; } - vCard.setAvatarType(type); vCard.setAvatarPath(path); From 7ce27d1c11d550fe4f423569eb3c2f8a4d6f0ddb Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 7 Apr 2020 23:33:03 +0300 Subject: [PATCH 3/9] pasword storing options: jammed an alwaysAsk, external lib for password jamming, changelog --- CHANGELOG.md | 74 ++++++++ CMakeLists.txt | 2 + core/CMakeLists.txt | 1 + core/squawk.cpp | 75 +++++++- core/squawk.h | 5 + external/simpleCrypt/CMakeLists.txt | 16 ++ external/simpleCrypt/simplecrypt.cpp | 252 +++++++++++++++++++++++++++ external/simpleCrypt/simplecrypt.h | 225 ++++++++++++++++++++++++ main.cpp | 2 + shared/enums.h | 6 +- shared/global.cpp | 4 +- ui/models/account.cpp | 4 + ui/squawk.cpp | 70 ++++++-- ui/squawk.h | 10 ++ 14 files changed, 728 insertions(+), 18 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 external/simpleCrypt/CMakeLists.txt create mode 100644 external/simpleCrypt/simplecrypt.cpp create mode 100644 external/simpleCrypt/simplecrypt.h diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d281fb8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,74 @@ +# Changelog + +## Squawk 0.1.4 (UNRELEASED) +------------------------------ +### New features +- several ways to manage your account password: + - store it in plain text with the config (like it always was) + - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is) + - ask the account password on each program launch + +### Bug fixes +- never updating MUC avatars now update +- going offline related segfault fix + + +## Squawk 0.1.3 (Mar 31, 2020) +------------------------------ +### New features +- delivery states for the messages +- delivery receipts now work for real +- avatars in conferences +- edited messages now display correctly +- restyling to get better look with different desktop themes + +### Bug fixes +- clickable links now detects better +- fixed lost messages that came with no ID +- avatar related fixes +- message carbons now get turned on only if the server supports them +- progress spinner fix +- files in dialog now have better comment + + +## Squawk 0.1.2 (Dec 25, 2019) +------------------------------ +### New features +- icons in roster for conferences +- pal avatar in dialog window +- avatars next to every message in dialog windows (not in conferences yet) +- roster window position and size now are stored in config +- expanded accounts and groups are stored in config +- availability (from top combobox) now is stored in config + +### Bug fixes +- segfault when sending more then one attached file +- wrong path and name of saving file +- wrong message syntax when attaching file and writing text in the save message +- problem with links highlighting in dialog +- project building fixes + + +## Squawk 0.1.1 (Nov 16, 2019) +------------------------------ +A lot of bug fixes, memory leaks fixes +### New features +- exchange files via HTTP File Upload +- download VCards of your contacts +- upload your VCard with information about your contact phones email addresses, names, career information and avatar +- avatars of your contacts in roster and in notifications + + +## Squawk 0.0.5 (Oct 10, 2019) +------------------------------ +### Features +- chat directly +- have multiple accounts +- add contacts +- remove contacts +- assign contact to different groups +- chat in MUCs +- join MUCs, leave them, keep them subscribed or unsubscribed +- download attachmets +- local history +- desktop notifications of new messages diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e9968c..aac3e75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,8 @@ endif() add_subdirectory(ui) add_subdirectory(core) +add_subdirectory(external/simpleCrypt) + target_link_libraries(squawk squawkUI) target_link_libraries(squawk squawkCORE) target_link_libraries(squawk uuid) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index fab36f2..ad37198 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -37,3 +37,4 @@ target_link_libraries(squawkCORE Qt5::Gui) target_link_libraries(squawkCORE Qt5::Xml) target_link_libraries(squawkCORE qxmpp) target_link_libraries(squawkCORE lmdb) +target_link_libraries(squawkCORE simpleCrypt) diff --git a/core/squawk.cpp b/core/squawk.cpp index e8dd1ae..8c70cb8 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -52,14 +52,31 @@ void Core::Squawk::stop() QSettings settings; settings.beginGroup("core"); settings.beginWriteArray("accounts"); + SimpleCrypt crypto(passwordHash); for (std::deque::size_type i = 0; i < accounts.size(); ++i) { settings.setArrayIndex(i); Account* acc = accounts[i]; + + Shared::AccountPassword ap = acc->getPasswordType(); + QString password; + + switch (ap) { + case Shared::AccountPassword::plain: + password = acc->getPassword(); + break; + case Shared::AccountPassword::jammed: + password = crypto.encryptToString(acc->getPassword()); + break; + default: + break; + } + settings.setValue("name", acc->getName()); settings.setValue("server", acc->getServer()); settings.setValue("login", acc->getLogin()); - settings.setValue("password", acc->getPassword()); + settings.setValue("password", password); settings.setValue("resource", acc->getResource()); + settings.setValue("passwordType", static_cast(ap)); } settings.endArray(); settings.endGroup(); @@ -318,12 +335,37 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapsecond; Shared::ConnectionState st = acc->getState(); + QMap::const_iterator mItr; + bool needToReconnect = false; - if (st != Shared::ConnectionState::disconnected) { + mItr = map.find("login"); + if (mItr != map.end()) { + needToReconnect = acc->getLogin() != mItr->toString(); + } + + if (!needToReconnect) { + mItr = map.find("password"); + if (mItr != map.end()) { + needToReconnect = acc->getPassword() != mItr->toString(); + } + } + if (!needToReconnect) { + mItr = map.find("server"); + if (mItr != map.end()) { + needToReconnect = acc->getServer() != mItr->toString(); + } + } + if (!needToReconnect) { + mItr = map.find("resource"); + if (mItr != map.end()) { + needToReconnect = acc->getResource() != mItr->toString(); + } + } + + if (needToReconnect && st != Shared::ConnectionState::disconnected) { acc->reconnect(); } - QMap::const_iterator mItr; mItr = map.find("login"); if (mItr != map.end()) { acc->setLogin(mItr->toString()); @@ -344,6 +386,11 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapsetServer(mItr->toString()); } + mItr = map.find("passwordType"); + if (mItr != map.end()) { + acc->setPasswordType(Shared::Global::fromInt(mItr->toInt())); + } + emit changeAccount(name, map); } @@ -570,6 +617,17 @@ void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card itr->second->uploadVCard(card); } +void Core::Squawk::responsePassword(const QString& account, const QString& password) +{ + AccountsMap::const_iterator itr = amap.find(account); + if (itr == amap.end()) { + qDebug() << "An attempt to set password to non existing account" << account << ", skipping"; + return; + } + itr->second->setPassword(password); + accountReady(); +} + void Core::Squawk::readSettings() { QSettings settings; @@ -614,5 +672,16 @@ void Core::Squawk::parseAccount( addAccount(login, server, password, name, resource, passwordType); accountReady(); break; + case Shared::AccountPassword::jammed: { + SimpleCrypt crypto(passwordHash); + QString decrypted = crypto.decryptToString(password); + addAccount(login, server, decrypted, name, resource, passwordType); + accountReady(); + } + break; + case Shared::AccountPassword::alwaysAsk: + addAccount(login, server, QString(), name, resource, passwordType); + emit requestPassword(name); + break; } } diff --git a/core/squawk.h b/core/squawk.h index 6d5f7d9..f96f7e8 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -32,6 +32,7 @@ #include "shared/message.h" #include "shared/global.h" #include "networkaccess.h" +#include "external/simpleCrypt/simplecrypt.h" namespace Core { @@ -73,6 +74,7 @@ signals: void uploadFileProgress(const QString& messageId, qreal value); void responseVCard(const QString& jid, const Shared::VCard& card); void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data); + void requestPassword(const QString& account); public slots: void start(); @@ -101,6 +103,7 @@ public slots: void downloadFileRequest(const QString& messageId, const QString& url); void requestVCard(const QString& account, const QString& jid); void uploadVCard(const QString& account, const Shared::VCard& card); + void responsePassword(const QString& account, const QString& password); private: typedef std::deque Accounts; @@ -155,6 +158,8 @@ private: const QString& resource, Shared::AccountPassword passwordType ); + + static const quint64 passwordHash = 0x08d054225ac4871d; }; } diff --git a/external/simpleCrypt/CMakeLists.txt b/external/simpleCrypt/CMakeLists.txt new file mode 100644 index 0000000..bdb62c6 --- /dev/null +++ b/external/simpleCrypt/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.0) +project(simplecrypt) + +set(CMAKE_AUTOMOC ON) + +find_package(Qt5Core CONFIG REQUIRED) + +set(simplecrypt_SRC + simplecrypt.cpp +) + +# Tell CMake to create the helloworld executable +add_library(simpleCrypt ${simplecrypt_SRC}) + +# Use the Widgets module from Qt 5. +target_link_libraries(simpleCrypt Qt5::Core) diff --git a/external/simpleCrypt/simplecrypt.cpp b/external/simpleCrypt/simplecrypt.cpp new file mode 100644 index 0000000..9bbec90 --- /dev/null +++ b/external/simpleCrypt/simplecrypt.cpp @@ -0,0 +1,252 @@ +/* + Copyright (c) 2011, Andre Somers + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Rathenau Instituut, Andre Somers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "simplecrypt.h" +#include +#include +#include +#include +#include +#include + +SimpleCrypt::SimpleCrypt(): +m_key(0), +m_compressionMode(CompressionAuto), +m_protectionMode(ProtectionChecksum), +m_lastError(ErrorNoError) +{ + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +} + +SimpleCrypt::SimpleCrypt(quint64 key): +m_key(key), +m_compressionMode(CompressionAuto), +m_protectionMode(ProtectionChecksum), +m_lastError(ErrorNoError) +{ + qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); + splitKey(); +} + +void SimpleCrypt::setKey(quint64 key) +{ + m_key = key; + splitKey(); +} + +void SimpleCrypt::splitKey() +{ + m_keyParts.clear(); + m_keyParts.resize(8); + for (int i=0;i<8;i++) { + quint64 part = m_key; + for (int j=i; j>0; j--) + part = part >> 8; + part = part & 0xff; + m_keyParts[i] = static_cast(part); + } +} + +QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext) +{ + QByteArray plaintextArray = plaintext.toUtf8(); + return encryptToByteArray(plaintextArray); +} + +QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext) +{ + if (m_keyParts.isEmpty()) { + qWarning() << "No key set."; + m_lastError = ErrorNoKeySet; + return QByteArray(); + } + + + QByteArray ba = plaintext; + + CryptoFlags flags = CryptoFlagNone; + if (m_compressionMode == CompressionAlways) { + ba = qCompress(ba, 9); //maximum compression + flags |= CryptoFlagCompression; + } else if (m_compressionMode == CompressionAuto) { + QByteArray compressed = qCompress(ba, 9); + if (compressed.count() < ba.count()) { + ba = compressed; + flags |= CryptoFlagCompression; + } + } + + QByteArray integrityProtection; + if (m_protectionMode == ProtectionChecksum) { + flags |= CryptoFlagChecksum; + QDataStream s(&integrityProtection, QIODevice::WriteOnly); + s << qChecksum(ba.constData(), ba.length()); + } else if (m_protectionMode == ProtectionHash) { + flags |= CryptoFlagHash; + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(ba); + + integrityProtection += hash.result(); + } + + //prepend a random char to the string + char randomChar = char(qrand() & 0xFF); + ba = randomChar + integrityProtection + ba; + + int pos(0); + char lastChar(0); + + int cnt = ba.count(); + + while (pos < cnt) { + ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar; + lastChar = ba.at(pos); + ++pos; + } + + QByteArray resultArray; + resultArray.append(char(0x03)); //version for future updates to algorithm + resultArray.append(char(flags)); //encryption flags + resultArray.append(ba); + + m_lastError = ErrorNoError; + return resultArray; +} + +QString SimpleCrypt::encryptToString(const QString& plaintext) +{ + QByteArray plaintextArray = plaintext.toUtf8(); + QByteArray cypher = encryptToByteArray(plaintextArray); + QString cypherString = QString::fromLatin1(cypher.toBase64()); + return cypherString; +} + +QString SimpleCrypt::encryptToString(QByteArray plaintext) +{ + QByteArray cypher = encryptToByteArray(plaintext); + QString cypherString = QString::fromLatin1(cypher.toBase64()); + return cypherString; +} + +QString SimpleCrypt::decryptToString(const QString &cyphertext) +{ + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray plaintextArray = decryptToByteArray(cyphertextArray); + QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size()); + + return plaintext; +} + +QString SimpleCrypt::decryptToString(QByteArray cypher) +{ + QByteArray ba = decryptToByteArray(cypher); + QString plaintext = QString::fromUtf8(ba, ba.size()); + + return plaintext; +} + +QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext) +{ + QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1()); + QByteArray ba = decryptToByteArray(cyphertextArray); + + return ba; +} + +QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher) +{ + if (m_keyParts.isEmpty()) { + qWarning() << "No key set."; + m_lastError = ErrorNoKeySet; + return QByteArray(); + } + + QByteArray ba = cypher; + + if( cypher.count() < 3 ) + return QByteArray(); + + char version = ba.at(0); + + if (version !=3) { //we only work with version 3 + m_lastError = ErrorUnknownVersion; + qWarning() << "Invalid version or not a cyphertext."; + return QByteArray(); + } + + CryptoFlags flags = CryptoFlags(ba.at(1)); + + ba = ba.mid(2); + int pos(0); + int cnt(ba.count()); + char lastChar = 0; + + while (pos < cnt) { + char currentChar = ba[pos]; + ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8); + lastChar = currentChar; + ++pos; + } + + ba = ba.mid(1); //chop off the random number at the start + + bool integrityOk(true); + if (flags.testFlag(CryptoFlagChecksum)) { + if (ba.length() < 2) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + quint16 storedChecksum; + { + QDataStream s(&ba, QIODevice::ReadOnly); + s >> storedChecksum; + } + ba = ba.mid(2); + quint16 checksum = qChecksum(ba.constData(), ba.length()); + integrityOk = (checksum == storedChecksum); + } else if (flags.testFlag(CryptoFlagHash)) { + if (ba.length() < 20) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + QByteArray storedHash = ba.left(20); + ba = ba.mid(20); + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(ba); + integrityOk = (hash.result() == storedHash); + } + + if (!integrityOk) { + m_lastError = ErrorIntegrityFailed; + return QByteArray(); + } + + if (flags.testFlag(CryptoFlagCompression)) + ba = qUncompress(ba); + + m_lastError = ErrorNoError; + return ba; +} diff --git a/external/simpleCrypt/simplecrypt.h b/external/simpleCrypt/simplecrypt.h new file mode 100644 index 0000000..2a5906a --- /dev/null +++ b/external/simpleCrypt/simplecrypt.h @@ -0,0 +1,225 @@ +/* + Copyright (c) 2011, Andre Somers + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Rathenau Instituut, Andre Somers nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SIMPLECRYPT_H +#define SIMPLECRYPT_H +#include +#include +#include + +/** + @ short Simple encrypt*ion and decryption of strings and byte arrays + + This class provides a simple implementation of encryption and decryption + of strings and byte arrays. + + @warning The encryption provided by this class is NOT strong encryption. It may + help to shield things from curious eyes, but it will NOT stand up to someone + determined to break the encryption. Don't say you were not warned. + + The class uses a 64 bit key. Simply create an instance of the class, set the key, + and use the encryptToString() method to calculate an encrypted version of the input string. + To decrypt that string again, use an instance of SimpleCrypt initialized with + the same key, and call the decryptToString() method with the encrypted string. If the key + matches, the decrypted version of the string will be returned again. + + If you do not provide a key, or if something else is wrong, the encryption and + decryption function will return an empty string or will return a string containing nonsense. + lastError() will return a value indicating if the method was succesful, and if not, why not. + + SimpleCrypt is prepared for the case that the encryption and decryption + algorithm is changed in a later version, by prepending a version identifier to the cypertext. + */ +class SimpleCrypt +{ +public: + /** + CompressionMode describes if compression will be applied to the data to be + encrypted. + */ + enum CompressionMode { + CompressionAuto, /*!< Only apply compression if that results in a shorter plaintext. */ + CompressionAlways, /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */ + CompressionNever /*!< Never apply compression. */ + }; + /** + IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data + or wrong decryption keys. + + Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This + increases the length of the resulting cypertext, but makes it possible to check if the plaintext + appears to be valid after decryption. + */ + enum IntegrityProtectionMode { + ProtectionNone, /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */ + ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */ + ProtectionHash /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */ + }; + /** + Error describes t*he type of error that occured. + */ + enum Error { + ErrorNoError, /*!< No error occurred. */ + ErrorNoKeySet, /*!< No key was set. You can not encrypt or decrypt without a valid key. */ + ErrorUnknownVersion, /*!< The version of this data is unknown, or the data is otherwise not valid. */ + ErrorIntegrityFailed, /*!< The integrity check of the data failed. Perhaps the wrong key was used. */ + }; + + /** + Constructor. * + + Constructs a SimpleCrypt instance without a valid key set on it. + */ + SimpleCrypt(); + /** + Constructor. * + + Constructs a SimpleCrypt instance and initializes it with the given @arg key. + */ + explicit SimpleCrypt(quint64 key); + + /** + ( Re-) initializes* the key with the given @arg key. + */ + void setKey(quint64 key); + /** + Returns true if SimpleCrypt has been initialized with a key. + */ + bool hasKey() const {return !m_keyParts.isEmpty();} + + /** + Sets the compress*ion mode to use when encrypting data. The default mode is Auto. + + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ + void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;} + /** + Returns the CompressionMode that is currently in use. + */ + CompressionMode compressionMode() const {return m_compressionMode;} + + /** + Sets the integrity mode to use when encrypting data. The default mode is Checksum. + + Note that decryption is not influenced by this mode, as the decryption recognizes + what mode was used when encrypting. + */ + void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;} + /** + Returns the IntegrityProtectionMode that is currently in use. + */ + IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;} + + /** + Returns the last *error that occurred. + */ + Error lastError() const {return m_lastError;} + + /** + Encrypts the @arg* plaintext string with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the string, so it can be stored easily in a text format. + */ + QString encryptToString(const QString& plaintext) ; + /** + Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns + a cyphertext the result. The result is a base64 encoded version of the binary array that is the + actual result of the encryption, so it can be stored easily in a text format. + */ + QString encryptToString(QByteArray plaintext) ; + /** + Encrypts the @arg* plaintext string with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. + + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ + QByteArray encryptToByteArray(const QString& plaintext) ; + /** + Encrypts the @arg* plaintext QByteArray with the key the class was initialized with, and returns + a binary cyphertext in a QByteArray the result. + + This method returns a byte array, that is useable for storing a binary format. If you need + a string you can store in a text file, use encryptToString() instead. + */ + QByteArray encryptToByteArray(QByteArray plaintext) ; + + /** + Decrypts a cypher*text string encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QString decryptToString(const QString& cyphertext) ; + /** + Decrypts a cypher*text string encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QByteArray decryptToByteArray(const QString& cyphertext) ; + /** + Decrypts a cypher*text binary encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QString decryptToString(QByteArray cypher) ; + /** + Decrypts a cypher*text binary encrypted with this class with the set key back to the + plain text version. + + If an error occured, such as non-matching keys between encryption and decryption, + an empty string or a string containing nonsense may be returned. + */ + QByteArray decryptToByteArray(QByteArray cypher) ; + + //enum to describe options that have been used for the encryption. Currently only one, but + //that only leaves room for future extensions like adding a cryptographic hash... + enum CryptoFlag{CryptoFlagNone = 0, + CryptoFlagCompression = 0x01, + CryptoFlagChecksum = 0x02, + CryptoFlagHash = 0x04 + }; + Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag); +private: + + void splitKey(); + + quint64 m_key; + QVector m_keyParts; + CompressionMode m_compressionMode; + IntegrityProtectionMode m_protectionMode; + Error m_lastError; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags) + +#endif // SimpleCrypt_H diff --git a/main.cpp b/main.cpp index eeb7138..b8be72c 100644 --- a/main.cpp +++ b/main.cpp @@ -116,6 +116,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest); QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard); QObject::connect(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard); + QObject::connect(&w, &Squawk::responsePassword, squawk, &Core::Squawk::responsePassword); QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); @@ -146,6 +147,7 @@ int main(int argc, char *argv[]) QObject::connect(squawk, &Core::Squawk::uploadFileProgress, &w, &Squawk::fileProgress); QObject::connect(squawk, &Core::Squawk::uploadFileError, &w, &Squawk::fileError); QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); + QObject::connect(squawk, &Core::Squawk::requestPassword, &w, &Squawk::requestPassword); QObject::connect(squawk, &Core::Squawk::ready, &w, &Squawk::readSettings); coreThread->start(); diff --git a/shared/enums.h b/shared/enums.h index bf55377..cb41443 100644 --- a/shared/enums.h +++ b/shared/enums.h @@ -110,11 +110,11 @@ static const std::deque messageStateThemeIcons = {"state-offline", "sta enum class AccountPassword { plain, jammed, - kwallet, - alwaysAsk + alwaysAsk, + kwallet }; Q_ENUM_NS(AccountPassword) -static const AccountPassword AccountPasswordHighest = AccountPassword::alwaysAsk; +static const AccountPassword AccountPasswordHighest = AccountPassword::kwallet; static const AccountPassword AccountPasswordLowest = AccountPassword::plain; } diff --git a/shared/global.cpp b/shared/global.cpp index 81bee3f..c974eaf 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -69,8 +69,8 @@ Shared::Global::Global(): accountPassword({ tr("Plain"), tr("Jammed"), - tr("KWallet"), - tr("Always Ask") + tr("Always Ask"), + tr("KWallet") }) { if (instance != 0) { diff --git a/ui/models/account.cpp b/ui/models/account.cpp index c581439..a60315c 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -39,6 +39,10 @@ Models::Account::Account(const QMap& data, Models::Item* pare if (aItr != data.end()) { setAvailability(aItr.value().toUInt()); } + QMap::const_iterator pItr = data.find("passwordType"); + if (pItr != data.end()) { + setPasswordType(pItr.value().toUInt()); + } } Models::Account::~Account() diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 6822dd2..3a11c1a 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -20,7 +20,6 @@ #include "ui_squawk.h" #include #include -#include Squawk::Squawk(QWidget *parent) : QMainWindow(parent), @@ -31,7 +30,9 @@ Squawk::Squawk(QWidget *parent) : contextMenu(new QMenu()), dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), requestedFiles(), - vCards() + vCards(), + requestedAccountsForPasswords(), + prompt(0) { m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); @@ -62,6 +63,18 @@ Squawk::Squawk(QWidget *parent) : if (testAttribute(Qt::WA_TranslucentBackground)) { m_ui->roster->viewport()->setAutoFillBackground(false); } + + QSettings settings; + settings.beginGroup("ui"); + settings.beginGroup("window"); + if (settings.contains("geometry")) { + restoreGeometry(settings.value("geometry").toByteArray()); + } + if (settings.contains("state")) { + restoreState(settings.value("state").toByteArray()); + } + settings.endGroup(); + settings.endGroup(); } Squawk::~Squawk() { @@ -871,14 +884,6 @@ void Squawk::readSettings() { QSettings settings; settings.beginGroup("ui"); - settings.beginGroup("window"); - if (settings.contains("geometry")) { - restoreGeometry(settings.value("geometry").toByteArray()); - } - if (settings.contains("state")) { - restoreState(settings.value("state").toByteArray()); - } - settings.endGroup(); if (settings.contains("availability")) { int avail = settings.value("availability").toInt(); @@ -958,3 +963,48 @@ void Squawk::onItemCollepsed(const QModelIndex& index) break; } } + +void Squawk::requestPassword(const QString& account) +{ + requestedAccountsForPasswords.push_back(account); + checkNextAccountForPassword(); +} + +void Squawk::checkNextAccountForPassword() +{ + if (prompt == 0 && requestedAccountsForPasswords.size() > 0) { + prompt = new QInputDialog(this); + QString accName = requestedAccountsForPasswords.front(); + connect(prompt, &QDialog::accepted, this, &Squawk::onPasswordPromptAccepted); + connect(prompt, &QDialog::rejected, this, &Squawk::onPasswordPromptRejected); + prompt->setInputMode(QInputDialog::TextInput); + prompt->setTextEchoMode(QLineEdit::Password); + prompt->setLabelText(tr("Input the password for account %1").arg(accName)); + prompt->setWindowTitle(tr("Password for account %1").arg(accName)); + prompt->setTextValue(""); + prompt->exec(); + } +} + +void Squawk::onPasswordPromptAccepted() +{ + emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue()); + onPasswordPromptDone(); +} + +void Squawk::onPasswordPromptDone() +{ + prompt->deleteLater(); + prompt = 0; + requestedAccountsForPasswords.pop_front(); + checkNextAccountForPassword(); +} + +void Squawk::onPasswordPromptRejected() +{ + //for now it's the same on reject and on accept, but one day I'm gonna make + //"Asking for the password again on the authentication failure" feature + //and here I'll be able to break the circle of password requests + emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue()); + onPasswordPromptDone(); +} diff --git a/ui/squawk.h b/ui/squawk.h index 64459ab..d5bde9c 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,7 @@ signals: void downloadFileRequest(const QString& messageId, const QString& url); void requestVCard(const QString& account, const QString& jid); void uploadVCard(const QString& account, const Shared::VCard& card); + void responsePassword(const QString& account, const QString& password); public slots: void readSettings(); @@ -107,6 +109,7 @@ public slots: void fileProgress(const QString& messageId, qreal value); void responseVCard(const QString& jid, const Shared::VCard& card); void changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data); + void requestPassword(const QString& account); private: typedef std::map Conversations; @@ -119,6 +122,8 @@ private: QDBusInterface dbus; std::map> requestedFiles; std::map vCards; + std::deque requestedAccountsForPasswords; + QInputDialog* prompt; protected: void closeEvent(QCloseEvent * event) override; @@ -146,7 +151,12 @@ private slots: void onConversationRequestLocalFile(const QString& messageId, const QString& url); void onConversationDownloadFile(const QString& messageId, const QString& url); void onItemCollepsed(const QModelIndex& index); + void onPasswordPromptAccepted(); + void onPasswordPromptRejected(); +private: + void checkNextAccountForPassword(); + void onPasswordPromptDone(); }; #endif // SQUAWK_H From 543538fc5667e304ca4788e24b70c4e7add15d1e Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 10 Apr 2020 01:48:08 +0300 Subject: [PATCH 4/9] first working prototype of dynamically loaded kwallet storage --- CHANGELOG.md | 5 - core/CMakeLists.txt | 4 +- core/passwordStorageEngines/CMakeLists.txt | 36 +++ core/passwordStorageEngines/kwallet.cpp | 227 ++++++++++++++++++ core/passwordStorageEngines/kwallet.h | 112 +++++++++ .../wrappers/kwallet.cpp | 33 +++ core/squawk.cpp | 77 +++++- core/squawk.h | 7 + 8 files changed, 484 insertions(+), 17 deletions(-) create mode 100644 core/passwordStorageEngines/CMakeLists.txt create mode 100644 core/passwordStorageEngines/kwallet.cpp create mode 100644 core/passwordStorageEngines/kwallet.h create mode 100644 core/passwordStorageEngines/wrappers/kwallet.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d281fb8..b993919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,6 @@ # Changelog ## Squawk 0.1.4 (UNRELEASED) ------------------------------- ### New features - several ways to manage your account password: - store it in plain text with the config (like it always was) @@ -14,7 +13,6 @@ ## Squawk 0.1.3 (Mar 31, 2020) ------------------------------- ### New features - delivery states for the messages - delivery receipts now work for real @@ -32,7 +30,6 @@ ## Squawk 0.1.2 (Dec 25, 2019) ------------------------------- ### New features - icons in roster for conferences - pal avatar in dialog window @@ -50,7 +47,6 @@ ## Squawk 0.1.1 (Nov 16, 2019) ------------------------------- A lot of bug fixes, memory leaks fixes ### New features - exchange files via HTTP File Upload @@ -60,7 +56,6 @@ A lot of bug fixes, memory leaks fixes ## Squawk 0.0.5 (Oct 10, 2019) ------------------------------- ### Features - chat directly - have multiple accounts diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index ad37198..32d61b9 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -23,13 +23,14 @@ set(squawkCORE_SRC # Tell CMake to create the helloworld executable add_library(squawkCORE ${squawkCORE_SRC}) +add_subdirectory(passwordStorageEngines) + if(SYSTEM_QXMPP) find_package(QXmpp CONFIG REQUIRED) get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES}) endif() - # Use the Widgets module from Qt 5. target_link_libraries(squawkCORE Qt5::Core) target_link_libraries(squawkCORE Qt5::Network) @@ -38,3 +39,4 @@ target_link_libraries(squawkCORE Qt5::Xml) target_link_libraries(squawkCORE qxmpp) target_link_libraries(squawkCORE lmdb) target_link_libraries(squawkCORE simpleCrypt) +target_link_libraries(squawkCORE kwalletPSE) diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt new file mode 100644 index 0000000..9527238 --- /dev/null +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.0) +project(pse) + +set(CMAKE_AUTOMOC ON) + +find_package(Qt5Core CONFIG REQUIRED) +find_package(Qt5Gui CONFIG REQUIRED) +find_package(KF5Wallet CONFIG REQUIRED) + +get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES) + +set(kwalletPSE_SRC + kwallet.cpp +) + +add_library(kwalletPSE ${kwalletPSE_SRC}) + +target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) +target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) + +target_link_libraries(kwalletPSE Qt5::Core) + +set(kwalletW_SRC + wrappers/kwallet.cpp +) + +add_library(kwalletWrapper SHARED ${kwalletW_SRC}) + +target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) +target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) + +target_link_libraries(kwalletWrapper KF5::Wallet) +target_link_libraries(kwalletWrapper Qt5::Core) + +install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/core/passwordStorageEngines/kwallet.cpp b/core/passwordStorageEngines/kwallet.cpp new file mode 100644 index 0000000..ddfb466 --- /dev/null +++ b/core/passwordStorageEngines/kwallet.cpp @@ -0,0 +1,227 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "kwallet.h" + +Core::PSE::KWallet::OpenWallet Core::PSE::KWallet::openWallet = 0; +Core::PSE::KWallet::NetworkWallet Core::PSE::KWallet::networkWallet = 0; +Core::PSE::KWallet::DeleteWallet Core::PSE::KWallet::deleteWallet = 0; +Core::PSE::KWallet::ReadPassword Core::PSE::KWallet::readPassword = 0; +Core::PSE::KWallet::WritePassword Core::PSE::KWallet::writePassword = 0; +Core::PSE::KWallet::HasFolder Core::PSE::KWallet::hasFolder = 0; +Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0; +Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0; + +Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial; +QLibrary Core::PSE::KWallet::lib("./libkwalletWrapper.so"); + +Core::PSE::KWallet::KWallet(): + QObject(), + cState(disconnected), + everError(false), + wallet(0), + readRequest(), + writeRequest() +{ + if (sState == initial) { + lib.load(); + + if (lib.isLoaded()) { + openWallet = (OpenWallet) lib.resolve("openWallet"); + networkWallet = (NetworkWallet) lib.resolve("networkWallet"); + deleteWallet = (DeleteWallet) lib.resolve("deleteWallet"); + readPassword = (ReadPassword) lib.resolve("readPassword"); + writePassword = (WritePassword) lib.resolve("writePassword"); + hasFolder = (HasFolder) lib.resolve("hasFolder"); + createFolder = (CreateFolder) lib.resolve("createFolder"); + setFolder = (SetFolder) lib.resolve("setFolder"); + + if (openWallet + && networkWallet + && deleteWallet + && readPassword + && writePassword + ) { + sState = success; + } else { + sState = failure; + } + } else { + sState = failure; + } + } +} + +Core::PSE::KWallet::~KWallet() +{ + close(); +} + +void Core::PSE::KWallet::open() +{ + if (sState == success) { + if (cState == disconnected) { + wallet = openWallet(networkWallet(), 0, ::KWallet::Wallet::Asynchronous); + if (wallet) { + cState = connecting; + connect(wallet, SIGNAL(walletOpened(bool)), this, SLOT(onWalletOpened(bool))); + connect(wallet, SIGNAL(walletClosed()), this, SLOT(onWalletClosed())); + } else { + everError = true; + emit opened(false); + } + } + } +} + +Core::PSE::KWallet::ConnectionState Core::PSE::KWallet::connectionState() +{ + return cState; +} + +void Core::PSE::KWallet::close() +{ + if (sState == success) { + if (cState != disconnected) { + deleteWallet(wallet); + wallet = 0; + } + rejectPending(); + } +} + +void Core::PSE::KWallet::onWalletClosed() +{ + cState = disconnected; + deleteWallet(wallet); + wallet = 0; + emit closed(); + rejectPending(); +} + +void Core::PSE::KWallet::onWalletOpened(bool success) +{ + emit opened(success); + if (success) { + QString appName = QCoreApplication::applicationName(); + if (!hasFolder(wallet, appName)) { + createFolder(wallet, appName); + } + setFolder(wallet, appName); + cState = connected; + readPending(); + } else { + everError = true; + cState = disconnected; + deleteWallet(wallet); + wallet = 0; + rejectPending(); + } +} + +Core::PSE::KWallet::SupportState Core::PSE::KWallet::supportState() +{ + return sState; +} + +bool Core::PSE::KWallet::everHadError() const +{ + return everError; +} + +void Core::PSE::KWallet::resetEverHadError() +{ + everError = false; +} + +void Core::PSE::KWallet::requestReadPassword(const QString& login, bool askAgain) +{ + if (sState == success) { + readRequest.insert(login); + readSwitch(askAgain); + } +} + +void Core::PSE::KWallet::requestWritePassword(const QString& login, const QString& password, bool askAgain) +{ + if (sState == success) { + std::map::iterator itr = writeRequest.find(login); + if (itr == writeRequest.end()) { + writeRequest.insert(std::make_pair(login, password)); + } else { + itr->second = password; + } + readSwitch(askAgain); + } +} + +void Core::PSE::KWallet::readSwitch(bool askAgain) +{ + switch (cState) { + case connected: + readPending(); + break; + case connecting: + break; + case disconnected: + if (!everError || askAgain) { + open(); + } + break; + } +} + +void Core::PSE::KWallet::rejectPending() +{ + writeRequest.clear(); + std::set::const_iterator i = readRequest.begin(); + while (i != readRequest.end()) { + emit rejectPassword(*i); + readRequest.erase(i); + i = readRequest.begin(); + } +} + +void Core::PSE::KWallet::readPending() +{ + std::map::const_iterator itr = writeRequest.begin(); + while (itr != writeRequest.end()) { + int result = writePassword(wallet, itr->first, itr->second); + if (result == 0) { + qDebug() << "Successfully saved password for user" << itr->first; + } else { + qDebug() << "Error writing password for user" << itr->first << ":" << result; + } + writeRequest.erase(itr); + itr = writeRequest.begin(); + } + + std::set::const_iterator i = readRequest.begin(); + while (i != readRequest.end()) { + QString password; + int result = readPassword(wallet, *i, password); + if (result == 0 && password.size() > 0) { //even though it's written that the error is supposed to be returned in case there were no password + emit responsePassword(*i, password); //it doesn't do so. I assume empty password as a lack of password in KWallet + } else { + emit rejectPassword(*i); + } + readRequest.erase(i); + i = readRequest.begin(); + } +} + diff --git a/core/passwordStorageEngines/kwallet.h b/core/passwordStorageEngines/kwallet.h new file mode 100644 index 0000000..8ac52d2 --- /dev/null +++ b/core/passwordStorageEngines/kwallet.h @@ -0,0 +1,112 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CORE_PSE_KWALLET_H +#define CORE_PSE_KWALLET_H + +#include +#include +#include +#include + +#include +#include + +#include + +namespace Core { +namespace PSE { + +/** + * @todo write docs + */ +class KWallet : public QObject +{ + Q_OBJECT +public: + enum SupportState { + initial, + success, + failure + }; + enum ConnectionState { + disconnected, + connecting, + connected + }; + + KWallet(); + ~KWallet(); + + static SupportState supportState(); + void open(); + void close(); + ConnectionState connectionState(); + bool everHadError() const; + void resetEverHadError(); + void requestReadPassword(const QString& login, bool askAgain = false); + void requestWritePassword(const QString& login, const QString& password, bool askAgain = false); + +signals: + void opened(bool success); + void closed(); + void responsePassword(const QString& login, const QString& password); + void rejectPassword(const QString& login); + +private slots: + void onWalletOpened(bool success); + void onWalletClosed(); + +private: + void readSwitch(bool askAgain); + void readPending(); + void rejectPending(); + +private: + typedef ::KWallet::Wallet* (*OpenWallet)(const QString &, WId, ::KWallet::Wallet::OpenType); + typedef const char* (*NetworkWallet)(); + typedef void (*DeleteWallet)(::KWallet::Wallet*); + typedef int (*ReadPassword)(::KWallet::Wallet*, const QString&, QString&); + typedef int (*WritePassword)(::KWallet::Wallet*, const QString&, const QString&); + typedef bool (*HasFolder)(::KWallet::Wallet* w, const QString &f); + typedef bool (*CreateFolder)(::KWallet::Wallet* w, const QString &f); + typedef bool (*SetFolder)(::KWallet::Wallet* w, const QString &f); + + static OpenWallet openWallet; + static NetworkWallet networkWallet; + static DeleteWallet deleteWallet; + static ReadPassword readPassword; + static WritePassword writePassword; + static HasFolder hasFolder; + static CreateFolder createFolder; + static SetFolder setFolder; + + static SupportState sState; + static QLibrary lib; + + ConnectionState cState; + bool everError; + ::KWallet::Wallet* wallet; + + std::set readRequest; + std::map writeRequest; +}; + +}} + +#endif // CORE_PSE_KWALLET_H diff --git a/core/passwordStorageEngines/wrappers/kwallet.cpp b/core/passwordStorageEngines/wrappers/kwallet.cpp new file mode 100644 index 0000000..39a2eaf --- /dev/null +++ b/core/passwordStorageEngines/wrappers/kwallet.cpp @@ -0,0 +1,33 @@ +#include + +extern "C" KWallet::Wallet* openWallet(const QString &name, WId w, KWallet::Wallet::OpenType ot = KWallet::Wallet::Synchronous) { + return KWallet::Wallet::openWallet(name, w, ot); +} + +extern "C" void deleteWallet(KWallet::Wallet* w) { + w->deleteLater(); +} + +extern "C" const char* networkWallet() { + return KWallet::Wallet::NetworkWallet().toStdString().c_str(); +} + +extern "C" int readPassword(KWallet::Wallet* w, const QString &key, QString &value) { + return w->readPassword(key, value); +} + +extern "C" int writePassword(KWallet::Wallet* w, const QString &key, const QString &value) { + return w->writePassword(key, value); +} + +extern "C" bool hasFolder(KWallet::Wallet* w, const QString &f) { + return w->hasFolder(f); +} + +extern "C" bool createFolder(KWallet::Wallet* w, const QString &f) { + return w->createFolder(f); +} + +extern "C" bool setFolder(KWallet::Wallet* w, const QString &f) { + return w->setFolder(f); +} diff --git a/core/squawk.cpp b/core/squawk.cpp index 8c70cb8..abd8977 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -27,13 +27,22 @@ Core::Squawk::Squawk(QObject* parent): accounts(), amap(), network(), - waitingForAccounts(0) + waitingForAccounts(0), + kwallet() { connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse); connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress); connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError); connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress); connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError); + + + if (kwallet.supportState() == PSE::KWallet::success) { + qDebug() << "KWallet support detected"; + connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened); + connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword); + connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword); + } } Core::Squawk::~Squawk() @@ -45,6 +54,11 @@ Core::Squawk::~Squawk() } } +void Core::Squawk::onWalletOpened(bool success) +{ + qDebug() << "KWallet opened: " << success; +} + void Core::Squawk::stop() { qDebug("Stopping squawk core.."); @@ -207,17 +221,27 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta Account* acc = static_cast(sender()); emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}}); - if (p_state == Shared::ConnectionState::disconnected) { - bool equals = true; - for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) { - if ((*itr)->getState() != Shared::ConnectionState::disconnected) { - equals = false; + switch (p_state) { + case Shared::ConnectionState::disconnected: { + bool equals = true; + for (Accounts::const_iterator itr = accounts.begin(), end = accounts.end(); itr != end; itr++) { + if ((*itr)->getState() != Shared::ConnectionState::disconnected) { + equals = false; + } + } + if (equals && state != Shared::Availability::offline) { + state = Shared::Availability::offline; + emit stateChanged(state); + } } - } - if (equals && state != Shared::Availability::offline) { - state = Shared::Availability::offline; - emit stateChanged(state); - } + break; + case Shared::ConnectionState::connected: + if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) { + kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true); + } + break; + default: + break; } } @@ -391,6 +415,13 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapsetPasswordType(Shared::Global::fromInt(mItr->toInt())); } + if (acc->getPasswordType() == Shared::AccountPassword::kwallet + && kwallet.supportState() == PSE::KWallet::success + && !needToReconnect + ) { + kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true); + } + emit changeAccount(name, map); } @@ -683,5 +714,29 @@ void Core::Squawk::parseAccount( addAccount(login, server, QString(), name, resource, passwordType); emit requestPassword(name); break; + case Shared::AccountPassword::kwallet: { + addAccount(login, server, QString(), name, resource, passwordType); + if (kwallet.supportState() == PSE::KWallet::success) { + kwallet.requestReadPassword(name); + } else { + emit requestPassword(name); + } + } } } + +void Core::Squawk::onWalletRejectPassword(const QString& login) +{ + emit requestPassword(login); +} + +void Core::Squawk::onWalletResponsePassword(const QString& login, const QString& password) +{ + AccountsMap::const_iterator itr = amap.find(login); + if (itr == amap.end()) { + qDebug() << "An attempt to set password to non existing account" << login << ", skipping"; + return; + } + itr->second->setPassword(password); + accountReady(); +} diff --git a/core/squawk.h b/core/squawk.h index f96f7e8..b1fa492 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -34,6 +34,8 @@ #include "networkaccess.h" #include "external/simpleCrypt/simplecrypt.h" +#include "passwordStorageEngines/kwallet.h" + namespace Core { class Squawk : public QObject @@ -114,6 +116,7 @@ private: Shared::Availability state; NetworkAccess network; uint8_t waitingForAccounts; + PSE::KWallet kwallet; private slots: void addAccount( @@ -147,6 +150,10 @@ private slots: void onAccountRemoveRoomPresence(const QString& jid, const QString& nick); void onAccountChangeMessage(const QString& jid, const QString& id, const QMap& data); + void onWalletOpened(bool success); + void onWalletResponsePassword(const QString& login, const QString& password); + void onWalletRejectPassword(const QString& login); + private: void readSettings(); void accountReady(); From b95028e33e2f8aaea255379a559fd7e93b14b838 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 11 Apr 2020 01:15:08 +0300 Subject: [PATCH 5/9] testing, ability to build without kwallet, translations, disabling unsupported storage types in combobox --- CHANGELOG.md | 1 + CMakeLists.txt | 30 +++- README.md | 10 ++ core/CMakeLists.txt | 8 +- core/passwordStorageEngines/CMakeLists.txt | 50 +++--- core/passwordStorageEngines/kwallet.cpp | 7 +- core/squawk.cpp | 20 ++- core/squawk.h | 5 + shared/global.cpp | 101 +++++++---- shared/global.h | 9 + translations/squawk.ru.ts | 196 +++++++++++---------- ui/widgets/account.cpp | 16 +- ui/widgets/account.h | 4 + ui/widgets/account.ui | 20 ++- ui/widgets/accounts.cpp | 1 + 15 files changed, 315 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b993919..11177e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - store it in plain text with the config (like it always was) - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is) - ask the account password on each program launch + - store it in KWallet which is dynamically loaded ### Bug fixes - never updating MUC avatars now update diff --git a/CMakeLists.txt b/CMakeLists.txt index aac3e75..20ae092 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,15 +62,39 @@ add_custom_target(translations ALL DEPENDS ${QM_FILES}) qt5_add_resources(RCC resources/resources.qrc) -add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC}) -target_link_libraries(squawk Qt5::Widgets) - option(SYSTEM_QXMPP "Use system qxmpp lib" ON) +option(WITH_KWALLET "Build KWallet support module" ON) + +if (SYSTEM_QXMPP) + find_package(QXmpp CONFIG) + + if (NOT QXmpp_FOUND) + set(SYSTEM_QXMPP OFF) + message("QXmpp package wasn't found, trying to build with bundled QXmpp") + else() + message("Building with system QXmpp") + endif() +endif() if(NOT SYSTEM_QXMPP) add_subdirectory(external/qxmpp) endif() +if (WITH_KWALLET) + find_package(KF5Wallet CONFIG) + + if (NOT KF5Wallet_FOUND) + set(WITH_KWALLET OFF) + message("KWallet package wasn't found, KWallet support module wouldn't be built") + else() + add_definitions(-DWITH_KWALLET) + message("Building with support of KWallet") + endif() +endif() + +add_executable(squawk ${squawk_SRC} ${squawk_HEAD} ${RCC}) +target_link_libraries(squawk Qt5::Widgets) + add_subdirectory(ui) add_subdirectory(core) diff --git a/README.md b/README.md index 1366c96..c820ccd 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,16 @@ $ cmake .. -D SYSTEM_QXMPP=False $ cmake --build . ``` +### List of keys + +Here is the list of keys you can pass to configuration phase of `cmake ..`. +- `CMAKE_BUILD_TYPE` - `Debug` just builds showing all warnings, `Release` builds with no warnings and applies optimizations (default is `Debug`) +- `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`) +- `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`) + + +Each key is supposed to be passed like that + ## License This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 32d61b9..c9c573b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -20,13 +20,13 @@ set(squawkCORE_SRC adapterFuctions.cpp ) +add_subdirectory(passwordStorageEngines) + # Tell CMake to create the helloworld executable add_library(squawkCORE ${squawkCORE_SRC}) -add_subdirectory(passwordStorageEngines) if(SYSTEM_QXMPP) - find_package(QXmpp CONFIG REQUIRED) get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES}) endif() @@ -39,4 +39,6 @@ target_link_libraries(squawkCORE Qt5::Xml) target_link_libraries(squawkCORE qxmpp) target_link_libraries(squawkCORE lmdb) target_link_libraries(squawkCORE simpleCrypt) -target_link_libraries(squawkCORE kwalletPSE) +if (WITH_KWALLET) + target_link_libraries(squawkCORE kwalletPSE) +endif() diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt index 9527238..36f67b1 100644 --- a/core/passwordStorageEngines/CMakeLists.txt +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -1,36 +1,42 @@ cmake_minimum_required(VERSION 3.0) project(pse) -set(CMAKE_AUTOMOC ON) -find_package(Qt5Core CONFIG REQUIRED) -find_package(Qt5Gui CONFIG REQUIRED) -find_package(KF5Wallet CONFIG REQUIRED) +if (WITH_KWALLET) + set(CMAKE_AUTOMOC ON) -get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES) -get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES) + find_package(Qt5Core CONFIG REQUIRED) + find_package(Qt5Gui CONFIG REQUIRED) -set(kwalletPSE_SRC - kwallet.cpp -) + get_target_property(KWALLET_INTERFACE_INCLUDE_DIRECTORIES KF5::Wallet INTERFACE_INCLUDE_DIRECTORIES) + get_target_property(Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES Qt5::Gui INTERFACE_INCLUDE_DIRECTORIES) -add_library(kwalletPSE ${kwalletPSE_SRC}) + set(kwalletPSE_SRC + kwallet.cpp + ) + + add_library(kwalletPSE ${kwalletPSE_SRC}) + + target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) + target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) -target_include_directories(kwalletPSE PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) -target_include_directories(kwalletPSE PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) + target_link_libraries(kwalletPSE Qt5::Core) -target_link_libraries(kwalletPSE Qt5::Core) + set(kwalletW_SRC + wrappers/kwallet.cpp + ) -set(kwalletW_SRC - wrappers/kwallet.cpp -) + add_library(kwalletWrapper SHARED ${kwalletW_SRC}) + + target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) + target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) + + target_link_libraries(kwalletWrapper KF5::Wallet) + target_link_libraries(kwalletWrapper Qt5::Core) + + install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() -add_library(kwalletWrapper SHARED ${kwalletW_SRC}) -target_include_directories(kwalletWrapper PUBLIC ${KWALLET_INTERFACE_INCLUDE_DIRECTORIES}) -target_include_directories(kwalletWrapper PUBLIC ${Qt5GUI_INTERFACE_INCLUDE_DIRECTORIES}) -target_link_libraries(kwalletWrapper KF5::Wallet) -target_link_libraries(kwalletWrapper Qt5::Core) -install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/core/passwordStorageEngines/kwallet.cpp b/core/passwordStorageEngines/kwallet.cpp index ddfb466..fe05a2c 100644 --- a/core/passwordStorageEngines/kwallet.cpp +++ b/core/passwordStorageEngines/kwallet.cpp @@ -28,7 +28,7 @@ Core::PSE::KWallet::CreateFolder Core::PSE::KWallet::createFolder = 0; Core::PSE::KWallet::SetFolder Core::PSE::KWallet::setFolder = 0; Core::PSE::KWallet::SupportState Core::PSE::KWallet::sState = Core::PSE::KWallet::initial; -QLibrary Core::PSE::KWallet::lib("./libkwalletWrapper.so"); +QLibrary Core::PSE::KWallet::lib("kwalletWrapper"); Core::PSE::KWallet::KWallet(): QObject(), @@ -41,6 +41,11 @@ Core::PSE::KWallet::KWallet(): if (sState == initial) { lib.load(); + if (!lib.isLoaded()) { //fallback from the build directory + lib.setFileName("./core/passwordStorageEngines/libkwalletWrapper.so"); + lib.load(); + } + if (lib.isLoaded()) { openWallet = (OpenWallet) lib.resolve("openWallet"); networkWallet = (NetworkWallet) lib.resolve("networkWallet"); diff --git a/core/squawk.cpp b/core/squawk.cpp index abd8977..8a486c0 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -27,8 +27,10 @@ Core::Squawk::Squawk(QObject* parent): accounts(), amap(), network(), - waitingForAccounts(0), - kwallet() + waitingForAccounts(0) +#ifdef WITH_KWALLET + ,kwallet() +#endif { connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse); connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress); @@ -36,13 +38,15 @@ Core::Squawk::Squawk(QObject* parent): connect(&network, &NetworkAccess::uploadFileProgress, this, &Squawk::uploadFileProgress); connect(&network, &NetworkAccess::uploadFileError, this, &Squawk::uploadFileError); - +#ifdef WITH_KWALLET if (kwallet.supportState() == PSE::KWallet::success) { - qDebug() << "KWallet support detected"; connect(&kwallet, &PSE::KWallet::opened, this, &Squawk::onWalletOpened); connect(&kwallet, &PSE::KWallet::rejectPassword, this, &Squawk::onWalletRejectPassword); connect(&kwallet, &PSE::KWallet::responsePassword, this, &Squawk::onWalletResponsePassword); + + Shared::Global::setSupported("KWallet", true); } +#endif } Core::Squawk::~Squawk() @@ -236,9 +240,11 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta } break; case Shared::ConnectionState::connected: +#ifdef WITH_KWALLET if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success) { kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true); } +#endif break; default: break; @@ -415,12 +421,14 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMapsetPasswordType(Shared::Global::fromInt(mItr->toInt())); } +#ifdef WITH_KWALLET if (acc->getPasswordType() == Shared::AccountPassword::kwallet && kwallet.supportState() == PSE::KWallet::success && !needToReconnect ) { kwallet.requestWritePassword(acc->getName(), acc->getPassword(), true); } +#endif emit changeAccount(name, map); } @@ -716,11 +724,15 @@ void Core::Squawk::parseAccount( break; case Shared::AccountPassword::kwallet: { addAccount(login, server, QString(), name, resource, passwordType); +#ifdef WITH_KWALLET if (kwallet.supportState() == PSE::KWallet::success) { kwallet.requestReadPassword(name); } else { +#endif emit requestPassword(name); +#ifdef WITH_KWALLET } +#endif } } } diff --git a/core/squawk.h b/core/squawk.h index b1fa492..31812d2 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -34,7 +34,9 @@ #include "networkaccess.h" #include "external/simpleCrypt/simplecrypt.h" +#ifdef WITH_KWALLET #include "passwordStorageEngines/kwallet.h" +#endif namespace Core { @@ -116,7 +118,10 @@ private: Shared::Availability state; NetworkAccess network; uint8_t waitingForAccounts; + +#ifdef WITH_KWALLET PSE::KWallet kwallet; +#endif private slots: void addAccount( diff --git a/shared/global.cpp b/shared/global.cpp index c974eaf..c8e5cf2 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -24,53 +24,62 @@ Shared::Global* Shared::Global::instance = 0; Shared::Global::Global(): availability({ - tr("Online"), - tr("Away"), - tr("Absent"), - tr("Busy"), - tr("Chatty"), - tr("Invisible"), - tr("Offline") + tr("Online", "Availability"), + tr("Away", "Availability"), + tr("Absent", "Availability"), + tr("Busy", "Availability"), + tr("Chatty", "Availability"), + tr("Invisible", "Availability"), + tr("Offline", "Availability") }), connectionState({ - tr("Disconnected"), - tr("Connecting"), - tr("Connected"), - tr("Error") + tr("Disconnected", "ConnectionState"), + tr("Connecting", "ConnectionState"), + tr("Connected", "ConnectionState"), + tr("Error", "ConnectionState") }), subscriptionState({ - tr("None"), - tr("From"), - tr("To"), - tr("Both"), - tr("Unknown") + tr("None", "SubscriptionState"), + tr("From", "SubscriptionState"), + tr("To", "SubscriptionState"), + tr("Both", "SubscriptionState"), + tr("Unknown", "SubscriptionState") }), affiliation({ - tr("Unspecified"), - tr("Outcast"), - tr("Nobody"), - tr("Member"), - tr("Admin"), - tr("Owner") + tr("Unspecified", "Affiliation"), + tr("Outcast", "Affiliation"), + tr("Nobody", "Affiliation"), + tr("Member", "Affiliation"), + tr("Admin", "Affiliation"), + tr("Owner", "Affiliation") }), role({ - tr("Unspecified"), - tr("Nobody"), - tr("Visitor"), - tr("Participant"), - tr("Moderator") + tr("Unspecified", "Role"), + tr("Nobody", "Role"), + tr("Visitor", "Role"), + tr("Participant", "Role"), + tr("Moderator", "Role") }), messageState({ - tr("Pending"), - tr("Sent"), - tr("Delivered"), - tr("Error") + tr("Pending", "MessageState"), + tr("Sent", "MessageState"), + tr("Delivered", "MessageState"), + tr("Error", "MessageState") }), accountPassword({ - tr("Plain"), - tr("Jammed"), - tr("Always Ask"), - tr("KWallet") + tr("Plain", "AccountPassword"), + tr("Jammed", "AccountPassword"), + tr("Always Ask", "AccountPassword"), + tr("KWallet", "AccountPassword") + }), + accountPasswordDescription({ + tr("Your password is going to be stored in config file in plain text", "AccountPasswordDescription"), + tr("Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it's not", "AccountPasswordDescription"), + tr("Squawk is going to query you for the password on every start of the program", "AccountPasswordDescription"), + tr("Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions", "AccountPasswordDescription") + }), + pluginSupport({ + {"KWallet", false} }) { if (instance != 0) { @@ -120,6 +129,28 @@ QString Shared::Global::getName(Shared::AccountPassword ap) return instance->accountPassword[static_cast(ap)]; } +void Shared::Global::setSupported(const QString& pluginName, bool support) +{ + std::map::iterator itr = instance->pluginSupport.find(pluginName); + if (itr != instance->pluginSupport.end()) { + itr->second = support; + } +} + +bool Shared::Global::supported(const QString& pluginName) +{ + std::map::iterator itr = instance->pluginSupport.find(pluginName); + if (itr != instance->pluginSupport.end()) { + return itr->second; + } + return false; +} + +QString Shared::Global::getDescription(Shared::AccountPassword ap) +{ + return instance->accountPasswordDescription[static_cast(ap)]; +} + #define FROM_INT_INPL(Enum) \ template<> \ Enum Shared::Global::fromInt(int src) \ diff --git a/shared/global.h b/shared/global.h index 3ea6147..481ac01 100644 --- a/shared/global.h +++ b/shared/global.h @@ -45,6 +45,8 @@ namespace Shared { static QString getName(Message::State rl); static QString getName(AccountPassword ap); + static QString getDescription(AccountPassword ap); + const std::deque availability; const std::deque connectionState; const std::deque subscriptionState; @@ -53,6 +55,11 @@ namespace Shared { const std::deque messageState; const std::deque accountPassword; + const std::deque accountPasswordDescription; + + static bool supported(const QString& pluginName); + static void setSupported(const QString& pluginName, bool support); + template static T fromInt(int src); @@ -74,6 +81,8 @@ namespace Shared { private: static Global* instance; + + std::map pluginSupport; }; } diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts index 8a733f2..32efc46 100644 --- a/translations/squawk.ru.ts +++ b/translations/squawk.ru.ts @@ -67,7 +67,7 @@ Password storage - + Хранение пароля @@ -122,173 +122,200 @@ p, li { white-space: pre-wrap; } Global - - Disconnected - Отключен - - - Connecting - Подключается - - - Connected - Подключен - - - Error - Ошибка - Online + Availability В сети Away + Availability Отошел - - Busy - Занят - Absent + Availability Недоступен + + Busy + Availability + Занят + Chatty + Availability Готов поболтать Invisible + Availability Невидимый Offline + Availability Отключен + + Disconnected + ConnectionState + Отключен + + + Connecting + ConnectionState + Подключается + + + Connected + ConnectionState + Подключен + + + Error + ConnectionState + Ошибка + None + SubscriptionState Нет From + SubscriptionState Входящая To + SubscriptionState Исходящая Both + SubscriptionState Взаимная Unknown + SubscriptionState Неизвестно Unspecified + Affiliation Не назначено Outcast + Affiliation Изгой Nobody + Affiliation Никто Member + Affiliation Участник Admin + Affiliation Администратор Owner + Affiliation Владелец + + Unspecified + Role + Не назначено + + + Nobody + Role + Никто + Visitor + Role Гость Participant + Role Участник Moderator + Role Модератор - - Not specified - Не указан - - - Personal - Личный - - - Business - Рабочий - - - Fax - Факс - - - Pager - Пэйджер - - - Voice - Стационарный - - - Cell - Мобильный - - - Video - Видеофон - - - Modem - Модем - - - Other - Другой - Pending - В процессе + MessageState + В процессе отправки Sent + MessageState Отправлено Delivered + MessageState Доставлено + + Error + MessageState + Ошибка + Plain - + AccountPassword + Открытый текст Jammed - - - - KWallet - + AccountPassword + Обфусцированный Always Ask - + AccountPassword + Всегда спрашивать + + + KWallet + AccountPassword + KWallet + + + Your password is going to be stored in config file but jammed with constant encryption key you can find in program source code. It might look like encryption but it's not + AccountPasswordDescription + Ваш пароль будет храниться в обфусцированном виде в конфигурационном файле. Обфускация производится с помощью постоянного числа, которое можно найти в исходном коде программы. Это может и выглядит как шифрование но им не является + + + Squawk is going to query you for the password on every start of the program + AccountPasswordDescription + Squawk будет спрашивать пароль от этой учетной записи каждый раз при запуске + + + Your password is going to be stored in config file in plain text + AccountPasswordDescription + Ваш пароль будет храниться в конфигурационном файле открытым текстром + + + Your password is going to be stored in KDE wallet storage (KWallet). You're going to be queried for permissions + AccountPasswordDescription + Ваш пароль будет храниться в бумажнике KDE (KWallet). В первый раз программа попросит разрешения для доступа к бумажнику @@ -337,10 +364,6 @@ p, li { white-space: pre-wrap; } Message - - Download - Скачать - Open Открыть @@ -383,25 +406,6 @@ You can try again Загружается... - - Models::Accounts - - Name - Имя - - - Server - Сервер - - - State - Состояние - - - Error - Ошибка - - Models::Room @@ -625,6 +629,14 @@ to be displayed as %1 Attached file Прикрепленный файл + + Input the password for account %1 + Введите пароль для учетной записи %1 + + + Password for account %1 + Пароль для учетной записи %1 + VCard diff --git a/ui/widgets/account.cpp b/ui/widgets/account.cpp index d417d4f..ba3af6b 100644 --- a/ui/widgets/account.cpp +++ b/ui/widgets/account.cpp @@ -23,13 +23,21 @@ Account::Account(): QDialog(), m_ui(new Ui::Account) { - m_ui->setupUi (this); + m_ui->setupUi(this); + + connect(m_ui->passwordType, qOverload(&QComboBox::currentIndexChanged), this, &Account::onComboboxChange); for (int i = static_cast(Shared::AccountPasswordLowest); i < static_cast(Shared::AccountPasswordHighest) + 1; ++i) { Shared::AccountPassword ap = static_cast(i); m_ui->passwordType->addItem(Shared::Global::getName(ap)); } m_ui->passwordType->setCurrentIndex(static_cast(Shared::AccountPassword::plain)); + + if (!Shared::Global::supported("KWallet")) { + QStandardItemModel *model = static_cast(m_ui->passwordType->model()); + QStandardItem *item = model->item(static_cast(Shared::AccountPassword::kwallet)); + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + } } Account::~Account() @@ -63,3 +71,9 @@ void Account::setData(const QMap& data) m_ui->resource->setText(data.value("resource").toString()); m_ui->passwordType->setCurrentIndex(data.value("passwordType").toInt()); } + +void Account::onComboboxChange(int index) +{ + QString description = Shared::Global::getDescription(Shared::Global::fromInt(index)); + m_ui->comment->setText(description); +} diff --git a/ui/widgets/account.h b/ui/widgets/account.h index 9732224..cbe8b9c 100644 --- a/ui/widgets/account.h +++ b/ui/widgets/account.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "shared/global.h" @@ -44,6 +45,9 @@ public: void setData(const QMap& data); void lockId(); +private slots: + void onComboboxChange(int index); + private: QScopedPointer m_ui; }; diff --git a/ui/widgets/account.ui b/ui/widgets/account.ui index 28cb389..a1879bc 100644 --- a/ui/widgets/account.ui +++ b/ui/widgets/account.ui @@ -114,14 +114,14 @@ - + Resource - + A resource name like "Home" or "Work" @@ -141,6 +141,22 @@ + + + + + 0 + 0 + + + + + + + true + + + diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts.cpp index 626915e..7f4a135 100644 --- a/ui/widgets/accounts.cpp +++ b/ui/widgets/accounts.cpp @@ -37,6 +37,7 @@ Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) : m_ui->tableView->setModel(model); connect(m_ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Accounts::onSelectionChanged); connect(p_model, &Models::Accounts::changed, this, &Accounts::updateConnectButton); + connect(m_ui->tableView, &QTableView::doubleClicked, this, &Accounts::onEditButton); } Accounts::~Accounts() = default; From a77dfd191ab5dcf47169b3a6eee33e83afad0f38 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 11 Apr 2020 23:00:15 +0300 Subject: [PATCH 6/9] single window mode --- CHANGELOG.md | 1 + README.md | 3 - core/passwordStorageEngines/CMakeLists.txt | 5 - ui/models/roster.cpp | 10 + ui/models/roster.h | 2 + ui/squawk.cpp | 212 ++++++++++++++++++--- ui/squawk.h | 3 + ui/squawk.ui | 148 ++++++++++---- ui/utils/messageline.cpp | 8 +- ui/utils/messageline.h | 1 + ui/widgets/conversation.cpp | 14 ++ ui/widgets/conversation.h | 4 + ui/widgets/conversation.ui | 8 +- 13 files changed, 345 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11177e8..2fb98c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Squawk 0.1.4 (UNRELEASED) ### New features +- message line now is in the same window with roster (new window dialog is still able to opened on double click) - several ways to manage your account password: - store it in plain text with the config (like it always was) - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is) diff --git a/README.md b/README.md index c820ccd..7d55eb5 100644 --- a/README.md +++ b/README.md @@ -67,9 +67,6 @@ Here is the list of keys you can pass to configuration phase of `cmake ..`. - `SYSTEM_QXMPP` - `True` tries to link against `qxmpp` installed in the system, `False` builds bundled `qxmpp` library (default is `True`) - `WITH_KWALLET` - `True` builds the `KWallet` capability module if `KWallet` is installed and if not goes to `False`. `False` disables `KWallet` support (default is `True`) - -Each key is supposed to be passed like that - ## License This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/core/passwordStorageEngines/CMakeLists.txt b/core/passwordStorageEngines/CMakeLists.txt index 36f67b1..e824f77 100644 --- a/core/passwordStorageEngines/CMakeLists.txt +++ b/core/passwordStorageEngines/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.0) project(pse) - if (WITH_KWALLET) set(CMAKE_AUTOMOC ON) @@ -36,7 +35,3 @@ if (WITH_KWALLET) install(TARGETS kwalletWrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() - - - - diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 609715f..42ea9f8 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -386,6 +386,16 @@ bool Models::Roster::ElId::operator <(const Models::Roster::ElId& other) const } } +bool Models::Roster::ElId::operator!=(const Models::Roster::ElId& other) const +{ + return !(operator == (other)); +} + +bool Models::Roster::ElId::operator==(const Models::Roster::ElId& other) const +{ + return (account == other.account) && (name == other.name); +} + void Models::Roster::onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector& roles) { if (tl.column() == 0) { diff --git a/ui/models/roster.h b/ui/models/roster.h index 50c6532..34c343c 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -109,6 +109,8 @@ public: const QString name; bool operator < (const ElId& other) const; + bool operator == (const ElId& other) const; + bool operator != (const ElId& other) const; }; }; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 3a11c1a..120123a 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -32,7 +32,8 @@ Squawk::Squawk(QWidget *parent) : requestedFiles(), vCards(), requestedAccountsForPasswords(), - prompt(0) + prompt(0), + currentConversation(0) { m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); @@ -55,6 +56,7 @@ Squawk::Squawk(QWidget *parent) : connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked); connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu); connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed); + connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged); connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged); //m_ui->mainToolBar->addWidget(m_ui->comboBox); @@ -74,6 +76,10 @@ Squawk::Squawk(QWidget *parent) : restoreState(settings.value("state").toByteArray()); } settings.endGroup(); + + if (settings.contains("splitter")) { + m_ui->splitter->restoreState(settings.value("splitter").toByteArray()); + } settings.endGroup(); } @@ -344,16 +350,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) if (conv != 0) { if (created) { conv->setAttribute(Qt::WA_DeleteOnClose); - - connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); - connect(conv, qOverload(&Conversation::sendMessage), this, qOverload(&Squawk::onConversationMessage)); - connect(conv, qOverload(&Conversation::sendMessage), - this, qOverload(&Squawk::onConversationMessage)); - connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); - connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile); - connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile); - connect(conv, &Conversation::shown, this, &Squawk::onConversationShown); - + subscribeConversation(conv); conversations.insert(std::make_pair(*id, conv)); if (created) { @@ -372,6 +369,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) } } + delete id; } } } @@ -387,9 +385,8 @@ void Squawk::onConversationClosed(QObject* parent) Conversation* conv = static_cast(sender()); Models::Roster::ElId id(conv->getAccount(), conv->getJid()); Conversations::const_iterator itr = conversations.find(id); - if (itr == conversations.end()) { - qDebug() << "Conversation has been closed but can not be found among other opened conversations, application is most probably going to crash"; - return; + if (itr != conversations.end()) { + conversations.erase(itr); } if (conv->isMuc) { Room* room = static_cast(conv); @@ -397,7 +394,6 @@ void Squawk::onConversationClosed(QObject* parent) emit setRoomJoined(id.account, id.name, false); } } - conversations.erase(itr); } void Squawk::onConversationDownloadFile(const QString& messageId, const QString& url) @@ -429,6 +425,9 @@ void Squawk::fileProgress(const QString& messageId, qreal value) if (c != conversations.end()) { c->second->responseFileProgress(messageId, value); } + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->responseFileProgress(messageId, value); + } } } } @@ -447,6 +446,9 @@ void Squawk::fileError(const QString& messageId, const QString& error) if (c != conversations.end()) { c->second->fileError(messageId, error); } + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->fileError(messageId, error); + } } requestedFiles.erase(itr); } @@ -466,6 +468,9 @@ void Squawk::fileLocalPathResponse(const QString& messageId, const QString& path if (c != conversations.end()) { c->second->responseLocalFile(messageId, path); } + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->responseLocalFile(messageId, path); + } } requestedFiles.erase(itr); @@ -490,18 +495,33 @@ void Squawk::onConversationRequestLocalFile(const QString& messageId, const QStr void Squawk::accountMessage(const QString& account, const Shared::Message& data) { const QString& from = data.getPenPalJid(); - Conversations::iterator itr = conversations.find({account, from}); + Models::Roster::ElId id({account, from}); + Conversations::iterator itr = conversations.find(id); + bool found = false; + + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->addMessage(data); + QApplication::alert(this); + if (!isVisible() && !data.getForwarded()) { + notify(account, data); + } + found = true; + } + if (itr != conversations.end()) { Conversation* conv = itr->second; conv->addMessage(data); QApplication::alert(conv); - if (conv->isMinimized()) { + if (!found && conv->isMinimized()) { rosterModel.addMessage(account, data); } if (!conv->isVisible() && !data.getForwarded()) { notify(account, data); } - } else { + found = true; + } + + if (!found) { rosterModel.addMessage(account, data); if (!data.getForwarded()) { QApplication::alert(this); @@ -512,14 +532,26 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data) void Squawk::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data) { - Conversations::iterator itr = conversations.find({account, jid}); + Models::Roster::ElId eid({account, jid}); + bool found = false; + + if (currentConversation != 0 && currentConversation->getId() == eid) { + currentConversation->changeMessage(id, data); + QApplication::alert(this); + found = true; + } + + Conversations::iterator itr = conversations.find(eid); if (itr != conversations.end()) { Conversation* conv = itr->second; conv->changeMessage(id, data); - if (conv->isMinimized()) { + if (!found && conv->isMinimized()) { rosterModel.changeMessage(account, jid, id, data); } - } else { + found = true; + } + + if (!found) { rosterModel.changeMessage(account, jid, id, data); } } @@ -559,13 +591,37 @@ void Squawk::onConversationMessage(const Shared::Message& msg) { Conversation* conv = static_cast(sender()); emit sendMessage(conv->getAccount(), msg); + Models::Roster::ElId id = conv->getId(); + + if (currentConversation != 0 && currentConversation->getId() == id) { + if (conv == currentConversation) { + Conversations::iterator itr = conversations.find(id); + if (itr != conversations.end()) { + itr->second->addMessage(msg); + } + } else { + currentConversation->addMessage(msg); + } + } } void Squawk::onConversationMessage(const Shared::Message& msg, const QString& path) { Conversation* conv = static_cast(sender()); + Models::Roster::ElId id = conv->getId(); std::map>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set())).first; - itr->second.insert(Models::Roster::ElId(conv->getAccount(), conv->getJid())); + itr->second.insert(id); + + if (currentConversation != 0 && currentConversation->getId() == id) { + if (conv == currentConversation) { + Conversations::iterator itr = conversations.find(id); + if (itr != conversations.end()) { + itr->second->appendMessageWithUpload(msg, path); + } + } else { + currentConversation->appendMessageWithUpload(msg, path); + } + } emit sendMessage(conv->getAccount(), msg, path); } @@ -580,6 +636,10 @@ void Squawk::responseArchive(const QString& account, const QString& jid, const s { Models::Roster::ElId id(account, jid); + if (currentConversation != 0 && currentConversation->getId() == id) { + currentConversation->responseArchive(list); + } + Conversations::const_iterator itr = conversations.find(id); if (itr != conversations.end()) { itr->second->responseArchive(list); @@ -603,6 +663,13 @@ void Squawk::removeAccount(const QString& account) ++itr; } } + + if (currentConversation != 0 && currentConversation->getAccount() == account) { + currentConversation->deleteLater(); + currentConversation = 0; + m_ui->filler->show(); + } + rosterModel.removeAccount(account); } @@ -761,7 +828,9 @@ void Squawk::onRosterContextMenu(const QPoint& point) unsub->setEnabled(active); connect(unsub, &QAction::triggered, [this, id]() { emit setRoomAutoJoin(id.account, id.name, false); - if (conversations.find(id) == conversations.end()) { //to leave the room if it's not opened in a conversation window + if (conversations.find(id) == conversations.end() + && (currentConversation == 0 || currentConversation->getId() != id) + ) { //to leave the room if it's not opened in a conversation window emit setRoomJoined(id.account, id.name, false); } }); @@ -770,7 +839,9 @@ void Squawk::onRosterContextMenu(const QPoint& point) unsub->setEnabled(active); connect(unsub, &QAction::triggered, [this, id]() { emit setRoomAutoJoin(id.account, id.name, true); - if (conversations.find(id) == conversations.end()) { //to join the room if it's not already joined + if (conversations.find(id) == conversations.end() + && (currentConversation == 0 || currentConversation->getId() != id) + ) { //to join the room if it's not already joined emit setRoomJoined(id.account, id.name, true); } }); @@ -897,7 +968,6 @@ void Squawk::readSettings() } // need to fix that settings.endArray(); } - settings.endGroup(); } @@ -910,6 +980,8 @@ void Squawk::writeSettings() settings.setValue("state", saveState()); settings.endGroup(); + settings.setValue("splitter", m_ui->splitter->saveState()); + settings.setValue("availability", m_ui->comboBox->currentIndex()); settings.beginWriteArray("connectedAccounts"); int size = rosterModel.accountsModel->rowCount(QModelIndex()); @@ -1008,3 +1080,93 @@ void Squawk::onPasswordPromptRejected() emit responsePassword(requestedAccountsForPasswords.front(), prompt->textValue()); onPasswordPromptDone(); } + +void Squawk::subscribeConversation(Conversation* conv) +{ + connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); + connect(conv, qOverload(&Conversation::sendMessage), this, qOverload(&Squawk::onConversationMessage)); + connect(conv, qOverload(&Conversation::sendMessage), + this, qOverload(&Squawk::onConversationMessage)); + connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); + connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile); + connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile); + connect(conv, &Conversation::shown, this, &Squawk::onConversationShown); +} + +void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +{ + if (current.isValid()) { + Models::Item* node = static_cast(current.internalPointer()); + Models::Contact* contact = 0; + Models::Room* room = 0; + QString res; + Models::Roster::ElId* id = 0; + switch (node->type) { + case Models::Item::contact: + contact = static_cast(node); + id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); + break; + case Models::Item::presence: + contact = static_cast(node->parentItem()); + id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); + res = node->getName(); + break; + case Models::Item::room: + room = static_cast(node); + id = new Models::Roster::ElId(room->getAccountName(), room->getJid()); + break; + default: + break; + } + + if (id != 0) { + if (currentConversation != 0) { + if (currentConversation->getJid() == id->name) { + if (contact != 0) { + currentConversation->setPalResource(res); + } + } else { + currentConversation->deleteLater(); + } + } else { + m_ui->filler->hide(); + } + + Models::Account* acc = rosterModel.getAccount(id->account); + Models::Contact::Messages deque; + if (contact != 0) { + currentConversation = new Chat(acc, contact); + contact->getMessages(deque); + } else if (room != 0) { + currentConversation = new Room(acc, room); + room->getMessages(deque); + + if (!room->getJoined()) { + emit setRoomJoined(id->account, id->name, true); + } + } + if (!testAttribute(Qt::WA_TranslucentBackground)) { + currentConversation->setFeedFrames(true, false, true, true); + } + + subscribeConversation(currentConversation); + for (Models::Contact::Messages::const_iterator itr = deque.begin(), end = deque.end(); itr != end; ++itr) { + currentConversation->addMessage(*itr); + } + + if (res.size() > 0) { + currentConversation->setPalResource(res); + } + + m_ui->splitter->insertWidget(1, currentConversation); + + delete id; + } else { + if (currentConversation != 0) { + currentConversation->deleteLater(); + currentConversation = 0; + m_ui->filler->show(); + } + } + } +} diff --git a/ui/squawk.h b/ui/squawk.h index d5bde9c..5b3d7cd 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -124,6 +124,7 @@ private: std::map vCards; std::deque requestedAccountsForPasswords; QInputDialog* prompt; + Conversation* currentConversation; protected: void closeEvent(QCloseEvent * event) override; @@ -153,10 +154,12 @@ private slots: void onItemCollepsed(const QModelIndex& index); void onPasswordPromptAccepted(); void onPasswordPromptRejected(); + void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); private: void checkNextAccountForPassword(); void onPasswordPromptDone(); + void subscribeConversation(Conversation* conv); }; #endif // SQUAWK_H diff --git a/ui/squawk.ui b/ui/squawk.ui index e09cd19..6c50024 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -6,8 +6,8 @@ 0 0 - 385 - 508 + 718 + 720 @@ -17,7 +17,7 @@ Qt::ToolButtonFollowStyle - + 0 @@ -30,42 +30,112 @@ 0 - - - + + + + Qt::Horizontal + + + 1 + + false - - - - - -1 - - - - - - - QFrame::NoFrame - - - QFrame::Sunken - - - true - - - true - - - true - - - false - - - false - + + + + 0 + 0 + + + + + 200 + 0 + + + + + 400 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Sunken + + + true + + + true + + + true + + + false + + + false + + + + + + + false + + + + + + -1 + + + + + + + + + 2 + 0 + + + + + + + <html><head/><body><p><span style=" font-size:26pt;">Please select a contact to start chatting</span></p></body></html> + + + Qt::AlignCenter + + + true + + + + + @@ -75,8 +145,8 @@ 0 0 - 385 - 22 + 718 + 27 diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp index deb9a45..ecd10a1 100644 --- a/ui/utils/messageline.cpp +++ b/ui/utils/messageline.cpp @@ -427,6 +427,12 @@ void MessageLine::fileError(const QString& messageId, const QString& error) } void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QString& path) +{ + appendMessageWithUploadNoSiganl(msg, path); + emit uploadFile(msg, path); +} + +void MessageLine::appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path) { message(msg, true); QString id = msg.getId(); @@ -436,9 +442,9 @@ void MessageLine::appendMessageWithUpload(const Shared::Message& msg, const QStr ui->showComment(tr("Uploading...")); uploading.insert(std::make_pair(id, ui)); uploadPaths.insert(std::make_pair(id, path)); - emit uploadFile(msg, path); } + void MessageLine::onUpload() { //TODO retry diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h index 277d429..104dc72 100644 --- a/ui/utils/messageline.h +++ b/ui/utils/messageline.h @@ -53,6 +53,7 @@ public: void fileError(const QString& messageId, const QString& error); void fileProgress(const QString& messageId, qreal progress); void appendMessageWithUpload(const Shared::Message& msg, const QString& path); + void appendMessageWithUploadNoSiganl(const Shared::Message& msg, const QString& path); void removeMessage(const QString& messageId); void setMyAvatarPath(const QString& p_path); void setPalAvatar(const QString& jid, const QString& path); diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 13cb881..d413506 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -210,6 +210,11 @@ void Conversation::onEnterPressed() } } +void Conversation::appendMessageWithUpload(const Shared::Message& data, const QString& path) +{ + line->appendMessageWithUploadNoSiganl(data, path); +} + void Conversation::onMessagesResize(int amount) { manualSliderChange = true; @@ -334,6 +339,11 @@ void Conversation::responseLocalFile(const QString& messageId, const QString& pa line->responseLocalFile(messageId, path); } +Models::Roster::ElId Conversation::getId() const +{ + return {getAccount(), getJid()}; +} + void Conversation::addAttachedFile(const QString& path) { QMimeDatabase db; @@ -397,6 +407,10 @@ void Conversation::onTextEditDocSizeChanged(const QSizeF& size) m_ui->messageEditor->setMaximumHeight(int(size.height())); } +void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left) +{ + static_cast(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left); +} bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event) { diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 6ce8cad..075bc31 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -26,6 +26,7 @@ #include "shared/message.h" #include "order.h" #include "ui/models/account.h" +#include "ui/models/roster.h" #include "ui/utils/messageline.h" #include "ui/utils/resizer.h" #include "ui/utils/flowlayout.h" @@ -72,6 +73,7 @@ public: QString getJid() const; QString getAccount() const; QString getPalResource() const; + Models::Roster::ElId getId() const; virtual void addMessage(const Shared::Message& data); void setPalResource(const QString& res); @@ -82,6 +84,8 @@ public: void responseFileProgress(const QString& messageId, qreal progress); virtual void setAvatar(const QString& path); void changeMessage(const QString& id, const QMap& data); + void setFeedFrames(bool top, bool right, bool bottom, bool left); + virtual void appendMessageWithUpload(const Shared::Message& data, const QString& path); signals: void sendMessage(const Shared::Message& message); diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index 4ff8b34..9b1321e 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -10,6 +10,12 @@ 658 + + + 2 + 0 + + 0 @@ -206,7 +212,7 @@ 0 0 520 - 389 + 392 From 29c7d31c895e9a3c7009f06c0930ffeb2deb0af2 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 12 Apr 2020 18:55:05 +0300 Subject: [PATCH 7/9] context menu now doesn't select items, just temporarily, statuses and messahes html wrapping fix --- CHANGELOG.md | 8 ++++--- shared/utils.cpp | 19 ++++++++++++++++ shared/utils.h | 2 ++ ui/squawk.cpp | 44 +++++++++++++++++++++++++++++++++---- ui/squawk.h | 2 ++ ui/squawk.ui | 6 +++++ ui/utils/message.cpp | 29 ++++++------------------ ui/utils/message.h | 1 + ui/widgets/conversation.cpp | 3 +-- ui/widgets/conversation.h | 2 ++ ui/widgets/conversation.ui | 20 ++++++++++++++++- 11 files changed, 104 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb98c7..c8c5e75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,18 @@ ## Squawk 0.1.4 (UNRELEASED) ### New features -- message line now is in the same window with roster (new window dialog is still able to opened on double click) -- several ways to manage your account password: +- message line now is in the same window with roster (new window dialog is still able to opened on context menu) +- several new ways to manage your account password: - store it in plain text with the config (like it always was) - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is) - ask the account password on each program launch - store it in KWallet which is dynamically loaded ### Bug fixes -- never updating MUC avatars now update +- never updating MUC avatars now get updated - going offline related segfault fix +- statuses now behave better: they wrap if they don't fit, you can select them, you can follow links from there +- messages and statuses don't loose content if you use < ore > symbols ## Squawk 0.1.3 (Mar 31, 2020) diff --git a/shared/utils.cpp b/shared/utils.cpp index 776c052..06247f8 100644 --- a/shared/utils.cpp +++ b/shared/utils.cpp @@ -27,3 +27,22 @@ QString Shared::generateUUID() uuid_unparse_lower(uuid, uuid_str); return uuid_str; } + + +static const QRegularExpression urlReg("(?\\1"); + return "

" + processed + "

"; +} diff --git a/shared/utils.h b/shared/utils.h index 218b53a..e9e3d29 100644 --- a/shared/utils.h +++ b/shared/utils.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -28,6 +29,7 @@ namespace Shared { QString generateUUID(); +QString processMessageBody(const QString& msg); static const std::vector colorPalette = { QColor(244, 27, 63), diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 120123a..3e1eb9e 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -33,7 +33,8 @@ Squawk::Squawk(QWidget *parent) : vCards(), requestedAccountsForPasswords(), prompt(0), - currentConversation(0) + currentConversation(0), + restoreSelection() { m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); @@ -53,12 +54,13 @@ Squawk::Squawk(QWidget *parent) : connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact); connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference); connect(m_ui->comboBox, qOverload(&QComboBox::activated), this, &Squawk::onComboboxActivated); - connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked); + //connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked); connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu); connect(m_ui->roster, &QTreeView::collapsed, this, &Squawk::onItemCollepsed); connect(m_ui->roster->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Squawk::onRosterSelectionChanged); connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged); + connect(contextMenu, &QMenu::aboutToHide, this, &Squawk::onContextAboutToHide); //m_ui->mainToolBar->addWidget(m_ui->comboBox); setWindowTitle(tr("Contact list")); @@ -1094,13 +1096,19 @@ void Squawk::subscribeConversation(Conversation* conv) } void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous) -{ +{ + if (restoreSelection.isValid() && restoreSelection == current) { + restoreSelection = QModelIndex(); + return; + } + if (current.isValid()) { Models::Item* node = static_cast(current.internalPointer()); Models::Contact* contact = 0; Models::Room* room = 0; QString res; Models::Roster::ElId* id = 0; + bool hasContext = true; switch (node->type) { case Models::Item::contact: contact = static_cast(node); @@ -1110,21 +1118,38 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn contact = static_cast(node->parentItem()); id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid()); res = node->getName(); + hasContext = false; break; case Models::Item::room: room = static_cast(node); id = new Models::Roster::ElId(room->getAccountName(), room->getJid()); break; + case Models::Item::participant: + room = static_cast(node->parentItem()); + id = new Models::Roster::ElId(room->getAccountName(), room->getJid()); + hasContext = false; + break; + case Models::Item::group: + hasContext = false; default: break; } + if (hasContext && QGuiApplication::mouseButtons() & Qt::RightButton) { + if (id != 0) { + delete id; + } + restoreSelection = previous; + return; + } + if (id != 0) { if (currentConversation != 0) { - if (currentConversation->getJid() == id->name) { + if (currentConversation->getId() == *id) { if (contact != 0) { currentConversation->setPalResource(res); } + return; } else { currentConversation->deleteLater(); } @@ -1168,5 +1193,16 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn m_ui->filler->show(); } } + } else { + if (currentConversation != 0) { + currentConversation->deleteLater(); + currentConversation = 0; + m_ui->filler->show(); + } } } + +void Squawk::onContextAboutToHide() +{ + m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect); +} diff --git a/ui/squawk.h b/ui/squawk.h index 5b3d7cd..28a2f17 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -125,6 +125,7 @@ private: std::deque requestedAccountsForPasswords; QInputDialog* prompt; Conversation* currentConversation; + QModelIndex restoreSelection; protected: void closeEvent(QCloseEvent * event) override; @@ -155,6 +156,7 @@ private slots: void onPasswordPromptAccepted(); void onPasswordPromptRejected(); void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous); + void onContextAboutToHide(); private: void checkNextAccountForPassword(); diff --git a/ui/squawk.ui b/ui/squawk.ui index 6c50024..19fdc4f 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -81,6 +81,12 @@ QFrame::Sunken + + QAbstractItemView::NoEditTriggers + + + false + true diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp index c5149ef..7a004bb 100644 --- a/ui/utils/message.cpp +++ b/ui/utils/message.cpp @@ -23,18 +23,6 @@ #include #include -const QRegularExpression urlReg("(?setBackgroundRole(QPalette::AlternateBase); body->setAutoFillBackground(true); - QString bd = msg.getBody(); - //bd.replace(imgReg, ""); - bd.replace(urlReg, "\\1"); - //bd.replace("\n", "
"); + QString bd = Shared::processMessageBody(msg.getBody()); text->setTextFormat(Qt::RichText); - text->setText("

" + bd + "

");; + text->setText(bd);; text->setTextInteractionFlags(text->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); text->setWordWrap(true); text->setOpenExternalLinks(true); @@ -308,13 +293,13 @@ bool Message::change(const QMap& data) { bool idChanged = msg.change(data); - QString bd = msg.getBody(); - //bd.replace(imgReg, ""); - bd.replace(urlReg, "\\1"); - text->setText(bd); - if (bd.size() > 0) { + QString body = msg.getBody(); + QString bd = Shared::processMessageBody(body); + if (body.size() > 0) { + text->setText(bd); text->show(); } else { + text->setText(body); text->hide(); } if (msg.getEdited()) { diff --git a/ui/utils/message.h b/ui/utils/message.h index fc3f178..eef93a1 100644 --- a/ui/utils/message.h +++ b/ui/utils/message.h @@ -34,6 +34,7 @@ #include "shared/message.h" #include "shared/icons.h" #include "shared/global.h" +#include "shared/utils.h" #include "resizer.h" #include "image.h" diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index d413506..50f7dcf 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -19,7 +19,6 @@ #include "conversation.h" #include "ui_conversation.h" #include "ui/utils/dropshadoweffect.h" -#include "shared/icons.h" #include #include @@ -308,7 +307,7 @@ void Conversation::onFileSelected() void Conversation::setStatus(const QString& status) { - statusLabel->setText(status); + statusLabel->setText(Shared::processMessageBody(status)); } void Conversation::onScrollResize() diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 075bc31..f64ae54 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -31,6 +31,8 @@ #include "ui/utils/resizer.h" #include "ui/utils/flowlayout.h" #include "ui/utils/badge.h" +#include "shared/icons.h" +#include "shared/utils.h" namespace Ui { diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index 9b1321e..d73875d 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -122,9 +122,24 @@
+ + + 0 + 0 + + + + true + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + @@ -134,9 +149,12 @@ Qt::Horizontal + + QSizePolicy::Preferred + - 40 + 0 20 From 21c7d65027a8c4ff8c9aa29d0f71bdb89c54fa80 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 13 Apr 2020 22:57:23 +0300 Subject: [PATCH 8/9] offline avatars in mucs --- CHANGELOG.md | 1 + core/account.cpp | 7 ++++- core/archive.cpp | 33 +++++++++++++++++++-- core/archive.h | 3 +- core/conference.cpp | 58 ++++++++++++++++++++++++++---------- core/conference.h | 15 ++++++++-- core/rosteritem.cpp | 35 +++++++++++++--------- core/rosteritem.h | 8 +++-- shared/global.cpp | 1 + shared/global.h | 4 +++ ui/models/room.cpp | 27 ++++++++++++++++- ui/models/room.h | 2 ++ ui/utils/messageline.cpp | 59 +++++++++++++++++++++++++++++++++---- ui/utils/messageline.h | 3 ++ ui/widgets/conversation.cpp | 10 +++++++ ui/widgets/room.cpp | 4 ++- 16 files changed, 225 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c5e75..e528bd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - going offline related segfault fix - statuses now behave better: they wrap if they don't fit, you can select them, you can follow links from there - messages and statuses don't loose content if you use < ore > symbols +- now avatars of those who are not in the MUC right now but was also display next to the message ## Squawk 0.1.3 (Mar 31, 2020) diff --git a/core/account.cpp b/core/account.cpp index 2f8238a..4c12761 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1063,6 +1063,7 @@ void Core::Account::onContactHistoryResponse(const std::list& l void Core::Account::onClientError(QXmppClient::Error err) { + qDebug() << "Error"; QString errorText; QString errorType; switch (err) { @@ -1140,6 +1141,9 @@ void Core::Account::onClientError(QXmppClient::Error err) case QXmppStanza::Error::UnexpectedRequest: errorText = "Unexpected request"; break; + case QXmppStanza::Error::PolicyViolation: + errorText = "Policy violation"; + break; } errorType = "Client stream error"; @@ -1367,7 +1371,8 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS {"autoJoin", conf->getAutoJoin()}, {"joined", conf->getJoined()}, {"nick", conf->getNick()}, - {"name", conf->getName()} + {"name", conf->getName()}, + {"avatars", conf->getAllAvatars()} }; Archive::AvatarInfo info; diff --git a/core/archive.cpp b/core/archive.cpp index bd159ca..50acf81 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -675,7 +675,7 @@ bool Core::Archive::dropAvatar(const std::string& resource) } } -bool Core::Archive::setAvatar(const QByteArray& data, bool generated, const QString& resource) +bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool generated, const QString& resource) { if (!opened) { throw Closed("setAvatar", jid.toStdString()); @@ -726,7 +726,9 @@ bool Core::Archive::setAvatar(const QByteArray& data, bool generated, const QStr MDB_val lmdbKey, lmdbData; QByteArray value; - AvatarInfo newInfo(ext, newHash, generated); + newInfo.type = ext; + newInfo.hash = newHash; + newInfo.autogenerated = generated; newInfo.serialize(&value); lmdbKey.mv_size = res.size(); lmdbKey.mv_data = (char*)res.c_str(); @@ -802,6 +804,33 @@ bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const std: } } +void Core::Archive::readAllResourcesAvatars(std::map& data) const +{ + if (!opened) { + throw Closed("readAllResourcesAvatars", jid.toStdString()); + } + + int rc; + MDB_val lmdbKey, lmdbData; + MDB_txn *txn; + MDB_cursor* cursor; + mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); + mdb_cursor_open(txn, avatars, &cursor); + rc = mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_FIRST); + + do { + std::string sId((char*)lmdbKey.mv_data, lmdbKey.mv_size); + QString res(sId.c_str()); + if (res != jid) { + data.emplace(res, AvatarInfo()); + data[res].deserialize((char*)lmdbData.mv_data, lmdbData.mv_size); + } + } while (mdb_cursor_get(cursor, &lmdbKey, &lmdbData, MDB_NEXT) == 0); + + mdb_cursor_close(cursor); + mdb_txn_abort(txn); +} + Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const { if (!opened) { diff --git a/core/archive.h b/core/archive.h index 6facf68..ef6ca23 100644 --- a/core/archive.h +++ b/core/archive.h @@ -56,9 +56,10 @@ public: std::list getBefore(int count, const QString& id); bool isFromTheBeginning(); void setFromTheBeginning(bool is); - bool setAvatar(const QByteArray& data, bool generated = false, const QString& resource = ""); + bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = ""); AvatarInfo getAvatarInfo(const QString& resource = "") const; bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const; + void readAllResourcesAvatars(std::map& data) const; public: const QString jid; diff --git a/core/conference.cpp b/core/conference.cpp index d745227..cda19fd 100644 --- a/core/conference.cpp +++ b/core/conference.cpp @@ -25,7 +25,8 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo nick(p_nick), room(p_room), joined(false), - autoJoin(p_autoJoin) + autoJoin(p_autoJoin), + exParticipants() { muc = true; name = p_name; @@ -44,6 +45,8 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo if (autoJoin) { room->join(); } + + archive->readAllResourcesAvatars(exParticipants); } Core::Conference::~Conference() @@ -140,8 +143,8 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) resource = ""; } - Archive::AvatarInfo info; - bool hasAvatar = readAvatarInfo(info, resource); + std::map::const_iterator itr = exParticipants.find(resource); + bool hasAvatar = itr != exParticipants.end(); if (resource.size() > 0) { QDateTime lastInteraction = pres.lastUserInteraction(); @@ -158,12 +161,12 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) }; if (hasAvatar) { - if (info.autogenerated) { + if (itr->second.autogenerated) { cData.insert("avatarState", static_cast(Shared::Avatar::valid)); } else { cData.insert("avatarState", static_cast(Shared::Avatar::autocreated)); } - cData.insert("avatarPath", avatarPath(resource) + "." + info.type); + cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type); } else { cData.insert("avatarState", static_cast(Shared::Avatar::empty)); cData.insert("avatarPath", ""); @@ -179,14 +182,14 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name) case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here break; case QXmppPresence::VCardUpdateNoPhoto: { //there is no photo, need to drop if any - if (!hasAvatar || !info.autogenerated) { + if (!hasAvatar || !itr->second.autogenerated) { setAutoGeneratedAvatar(resource); } } break; case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load if (hasAvatar) { - if (info.autogenerated || info.hash != pres.photoHash()) { + if (itr->second.autogenerated || itr->second.hash != pres.photoHash()) { emit requestVCard(p_name); } } else { @@ -285,30 +288,46 @@ void Core::Conference::handlePresence(const QXmppPresence& pres) bool Core::Conference::setAutoGeneratedAvatar(const QString& resource) { - bool result = RosterItem::setAutoGeneratedAvatar(resource); + Archive::AvatarInfo newInfo; + bool result = RosterItem::setAutoGeneratedAvatar(newInfo, resource); if (result && resource.size() != 0) { + std::map::iterator itr = exParticipants.find(resource); + if (itr == exParticipants.end()) { + exParticipants.insert(std::make_pair(resource, newInfo)); + } else { + itr->second = newInfo; + } emit changeParticipant(resource, { {"avatarState", static_cast(Shared::Avatar::autocreated)}, - {"avatarPath", avatarPath(resource) + ".png"} + {"avatarPath", avatarPath(resource) + "." + newInfo.type} }); } return result; } -bool Core::Conference::setAvatar(const QByteArray& data, const QString& resource) +bool Core::Conference::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) { - bool result = RosterItem::setAvatar(data, resource); + bool result = RosterItem::setAvatar(data, info, resource); if (result && resource.size() != 0) { if (data.size() > 0) { - QMimeDatabase db; - QMimeType type = db.mimeTypeForData(data); - QString ext = type.preferredSuffix(); + std::map::iterator itr = exParticipants.find(resource); + if (itr == exParticipants.end()) { + exParticipants.insert(std::make_pair(resource, info)); + } else { + itr->second = info; + } + emit changeParticipant(resource, { {"avatarState", static_cast(Shared::Avatar::autocreated)}, - {"avatarPath", avatarPath(resource) + "." + ext} + {"avatarPath", avatarPath(resource) + "." + info.type} }); } else { + std::map::iterator itr = exParticipants.find(resource); + if (itr != exParticipants.end()) { + exParticipants.erase(itr); + } + emit changeParticipant(resource, { {"avatarState", static_cast(Shared::Avatar::empty)}, {"avatarPath", ""} @@ -333,3 +352,12 @@ Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, co return result; } + +QMap Core::Conference::getAllAvatars() const +{ + QMap result; + for (const std::pair& pair : exParticipants) { + result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type); + } + return result; +} diff --git a/core/conference.h b/core/conference.h index c00c472..4e0e463 100644 --- a/core/conference.h +++ b/core/conference.h @@ -19,9 +19,15 @@ #ifndef CORE_CONFERENCE_H #define CORE_CONFERENCE_H -#include "rosteritem.h" +#include + #include +#include + +#include "rosteritem.h" +#include "shared/global.h" + namespace Core { @@ -46,8 +52,8 @@ public: void setAutoJoin(bool p_autoJoin); void handlePresence(const QXmppPresence & pres) override; bool setAutoGeneratedAvatar(const QString& resource = "") override; - bool setAvatar(const QByteArray &data, const QString &resource = "") override; Shared::VCard handleResponseVCard(const QXmppVCardIq & card, const QString &resource) override; + QMap getAllAvatars() const; signals: void nickChanged(const QString& nick); @@ -58,11 +64,16 @@ signals: void changeParticipant(const QString& name, const QMap& data); void removeParticipant(const QString& name); +protected: + bool setAvatar(const QByteArray &data, Archive::AvatarInfo& info, const QString &resource = "") override; + private: QString nick; QXmppMucRoom* room; bool joined; bool autoJoin; + std::map exParticipants; + static const std::set supportedList; private slots: void onRoomJoined(); diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index 59b84f8..c25b339 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -410,28 +410,37 @@ bool Core::RosterItem::isMuc() const QString Core::RosterItem::avatarPath(const QString& resource) const { - QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - path += "/" + account + "/" + jid + "/" + (resource.size() == 0 ? jid : resource); + QString path = folderPath() + "/" + (resource.size() == 0 ? jid : resource); return path; } -bool Core::RosterItem::setAvatar(const QByteArray& data, const QString& resource) +QString Core::RosterItem::folderPath() const { - bool result = archive->setAvatar(data, false, resource); + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + account + "/" + jid; + return path; +} + +bool Core::RosterItem::setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource) +{ + bool result = archive->setAvatar(data, info, false, resource); if (resource.size() == 0 && result) { if (data.size() == 0) { emit avatarChanged(Shared::Avatar::empty, ""); } else { - QMimeDatabase db; - QMimeType type = db.mimeTypeForData(data); - QString ext = type.preferredSuffix(); - emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + ext); + emit avatarChanged(Shared::Avatar::valid, avatarPath(resource) + "." + info.type); } } return result; } bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource) +{ + Archive::AvatarInfo info; + return setAutoGeneratedAvatar(info, resource); +} + +bool Core::RosterItem::setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource) { QImage image(96, 96, QImage::Format_ARGB32_Premultiplied); QPainter painter(&image); @@ -453,7 +462,7 @@ bool Core::RosterItem::setAutoGeneratedAvatar(const QString& resource) stream.open(QBuffer::WriteOnly); image.save(&stream, "PNG"); stream.close(); - bool result = archive->setAvatar(arr, true, resource); + bool result = archive->setAvatar(arr, info, true, resource); if (resource.size() == 0 && result) { emit avatarChanged(Shared::Avatar::autocreated, avatarPath(resource) + ".png"); } @@ -468,6 +477,7 @@ bool Core::RosterItem::readAvatarInfo(Archive::AvatarInfo& target, const QString Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, const QString& resource) { Archive::AvatarInfo info; + Archive::AvatarInfo newInfo; bool hasAvatar = readAvatarInfo(info, resource); QByteArray ava = card.photo(); @@ -477,13 +487,10 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co QString path = ""; if (ava.size() > 0) { - bool changed = setAvatar(ava, resource); + bool changed = setAvatar(ava, newInfo, resource); if (changed) { type = Shared::Avatar::valid; - QMimeDatabase db; - QMimeType type = db.mimeTypeForData(ava); - QString ext = type.preferredSuffix(); - path = avatarPath(resource) + "." + ext; + path = avatarPath(resource) + "." + newInfo.type; } else if (hasAvatar) { if (info.autogenerated) { type = Shared::Avatar::autocreated; diff --git a/core/rosteritem.h b/core/rosteritem.h index 387ebfc..47470b1 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -69,8 +69,8 @@ public: void requestHistory(int count, const QString& before); void requestFromEmpty(int count, const QString& before); QString avatarPath(const QString& resource = "") const; + QString folderPath() const; bool readAvatarInfo(Archive::AvatarInfo& target, const QString& resource = "") const; - virtual bool setAvatar(const QByteArray& data, const QString& resource = ""); virtual bool setAutoGeneratedAvatar(const QString& resource = ""); virtual Shared::VCard handleResponseVCard(const QXmppVCardIq& card, const QString& resource); virtual void handlePresence(const QXmppPresence& pres) = 0; @@ -89,6 +89,10 @@ public: const QString jid; const QString account; +protected: + virtual bool setAvatar(const QByteArray& data, Archive::AvatarInfo& info, const QString& resource = ""); + virtual bool setAutoGeneratedAvatar(Archive::AvatarInfo& info, const QString& resource = ""); + protected: QString name; ArchiveState archiveState; @@ -103,7 +107,7 @@ protected: std::list> requestCache; std::map toCorrect; bool muc; - + private: void nextRequest(); void performRequest(int count, const QString& before); diff --git a/shared/global.cpp b/shared/global.cpp index c8e5cf2..a6b7b60 100644 --- a/shared/global.cpp +++ b/shared/global.cpp @@ -21,6 +21,7 @@ #include "enums.h" Shared::Global* Shared::Global::instance = 0; +const std::set Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"}; Shared::Global::Global(): availability({ diff --git a/shared/global.h b/shared/global.h index 481ac01..54e1584 100644 --- a/shared/global.h +++ b/shared/global.h @@ -24,6 +24,8 @@ #include "exception.h" #include +#include +#include #include #include @@ -60,6 +62,8 @@ namespace Shared { static bool supported(const QString& pluginName); static void setSupported(const QString& pluginName, bool support); + static const std::set supportedImagesExts; + template static T fromInt(int src); diff --git a/ui/models/room.cpp b/ui/models/room.cpp index be92d41..cc19d2c 100644 --- a/ui/models/room.cpp +++ b/ui/models/room.cpp @@ -32,7 +32,8 @@ Models::Room::Room(const QString& p_jid, const QMap& data, Mo avatarState(Shared::Avatar::empty), avatarPath(""), messages(), - participants() + participants(), + exParticipantAvatars() { QMap::const_iterator itr = data.find("autoJoin"); if (itr != data.end()) { @@ -62,6 +63,15 @@ Models::Room::Room(const QString& p_jid, const QMap& data, Mo if (itr != data.end()) { setAvatarPath(itr.value().toString()); } + + + itr = data.find("avatars"); + if (itr != data.end()) { + QMap avs = itr.value().toMap(); + for (QMap::const_iterator itr = avs.begin(), end = avs.end(); itr != end; ++itr) { + exParticipantAvatars.insert(std::make_pair(itr.key(), itr.value().toString())); + } + } } Models::Room::~Room() @@ -284,6 +294,11 @@ void Models::Room::addParticipant(const QString& p_name, const QMap::const_iterator eitr = exParticipantAvatars.find(name); + if (eitr != exParticipantAvatars.end()) { + exParticipantAvatars.erase(eitr); + } + Participant* part = new Participant(data); part->setName(p_name); participants.insert(std::make_pair(p_name, part)); @@ -311,6 +326,11 @@ void Models::Room::removeParticipant(const QString& p_name) Participant* p = itr->second; participants.erase(itr); removeChild(p->row()); + + if (p->getAvatarState() != Shared::Avatar::empty) { + exParticipantAvatars.insert(std::make_pair(p_name, p->getAvatarPath())); + } + p->deleteLater(); emit participantLeft(p_name); } @@ -408,3 +428,8 @@ QString Models::Room::getParticipantIconPath(const QString& name) const return itr->second->getAvatarPath(); } + +std::map Models::Room::getExParticipantAvatars() const +{ + return exParticipantAvatars; +} diff --git a/ui/models/room.h b/ui/models/room.h index 382b6e9..9ea70bf 100644 --- a/ui/models/room.h +++ b/ui/models/room.h @@ -75,6 +75,7 @@ public: QString getAvatarPath() const; std::map getParticipants() const; QString getParticipantIconPath(const QString& name) const; + std::map getExParticipantAvatars() const; signals: void participantJoined(const Participant& participant); @@ -99,6 +100,7 @@ private: QString avatarPath; Messages messages; std::map participants; + std::map exParticipantAvatars; }; diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp index ecd10a1..0ef5f07 100644 --- a/ui/utils/messageline.cpp +++ b/ui/utils/messageline.cpp @@ -29,6 +29,7 @@ MessageLine::MessageLine(bool p_room, QWidget* parent): palMessages(), uploadPaths(), palAvatars(), + exPalAvatars(), layout(new QVBoxLayout(this)), myName(), myAvatarPath(), @@ -80,6 +81,11 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg, bool forc std::map::iterator aItr = palAvatars.find(sender); if (aItr != palAvatars.end()) { aPath = aItr->second; + } else { + aItr = exPalAvatars.find(sender); + if (aItr != exPalAvatars.end()) { + aPath = aItr->second; + } } outgoing = false; } @@ -248,6 +254,11 @@ void MessageLine::setPalAvatar(const QString& jid, const QString& path) std::map::iterator itr = palAvatars.find(jid); if (itr == palAvatars.end()) { palAvatars.insert(std::make_pair(jid, path)); + + std::map::const_iterator eitr = exPalAvatars.find(jid); + if (eitr != exPalAvatars.end()) { + exPalAvatars.erase(eitr); + } } else { itr->second = path; } @@ -265,16 +276,36 @@ void MessageLine::dropPalAvatar(const QString& jid) std::map::iterator itr = palAvatars.find(jid); if (itr != palAvatars.end()) { palAvatars.erase(itr); - - std::map::iterator pItr = palMessages.find(jid); - if (pItr != palMessages.end()) { - for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) { - itr->second->setAvatarPath(""); - } + } + + std::map::const_iterator eitr = exPalAvatars.find(jid); + if (eitr != exPalAvatars.end()) { + exPalAvatars.erase(eitr); + } + + std::map::iterator pItr = palMessages.find(jid); + if (pItr != palMessages.end()) { + for (Index::const_iterator itr = pItr->second.begin(), end = pItr->second.end(); itr != end; ++itr) { + itr->second->setAvatarPath(""); } } } +void MessageLine::movePalAvatarToEx(const QString& name) +{ + std::map::iterator itr = palAvatars.find(name); + if (itr != palAvatars.end()) { + std::map::iterator eitr = exPalAvatars.find(name); + if (eitr != exPalAvatars.end()) { + eitr->second = itr->second; + } else { + exPalAvatars.insert(std::make_pair(name, itr->second)); + } + + palAvatars.erase(itr); + } +} + void MessageLine::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); @@ -459,3 +490,19 @@ void MessageLine::setMyAvatarPath(const QString& p_path) } } } + +void MessageLine::setExPalAvatars(const std::map& data) +{ + exPalAvatars = data; + + for (const std::pair& pair : palMessages) { + if (palAvatars.find(pair.first) == palAvatars.end()) { + std::map::const_iterator eitr = exPalAvatars.find(pair.first); + if (eitr != exPalAvatars.end()) { + for (const std::pair& mp : pair.second) { + mp.second->setAvatarPath(eitr->second); + } + } + } + } +} diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h index 104dc72..a0a7b6c 100644 --- a/ui/utils/messageline.h +++ b/ui/utils/messageline.h @@ -59,6 +59,8 @@ public: void setPalAvatar(const QString& jid, const QString& path); void dropPalAvatar(const QString& jid); void changeMessage(const QString& id, const QMap& data); + void setExPalAvatars(const std::map& data); + void movePalAvatarToEx(const QString& name); signals: void resize(int amount); @@ -90,6 +92,7 @@ private: std::map palMessages; std::map uploadPaths; std::map palAvatars; + std::map exPalAvatars; QVBoxLayout* layout; QString myName; diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index 50f7dcf..e677bc8 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -92,6 +92,16 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c line->setMyAvatarPath(acc->getAvatarPath()); line->setMyName(acc->getName()); + QFont nf = m_ui->nameLabel->font(); + nf.setBold(true); + nf.setPointSize(nf.pointSize() + 2); + m_ui->nameLabel->setFont(nf); + + QFont sf = statusLabel->font(); + sf.setItalic(true); + sf.setPointSize(sf.pointSize() - 2); + statusLabel->setFont(sf); + applyVisualEffects(); } diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp index 6d5340f..66ae5f7 100644 --- a/ui/widgets/room.cpp +++ b/ui/widgets/room.cpp @@ -38,6 +38,8 @@ Room::Room(Models::Account* acc, Models::Room* p_room, QWidget* parent): line->setPalAvatar(pair.first, aPath); } } + + line->setExPalAvatars(room->getExParticipantAvatars()); } Room::~Room() @@ -104,5 +106,5 @@ void Room::onParticipantJoined(const Models::Participant& participant) void Room::onParticipantLeft(const QString& name) { - line->dropPalAvatar(name); + line->movePalAvatarToEx(name); } From cb44b12a7ec9b303294eb863e10eedd19159b909 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 14 Apr 2020 19:30:33 +0300 Subject: [PATCH 9/9] 0.1.4 kwallet optimisation related fix, DnD files into convs, visual fixes --- CHANGELOG.md | 4 +- CMakeLists.txt | 12 +-- README.md | 3 +- core/passwordStorageEngines/kwallet.cpp | 10 +- core/passwordStorageEngines/kwallet.h | 2 +- .../wrappers/kwallet.cpp | 4 +- main.cpp | 2 +- order.h | 2 +- packaging/Archlinux/PKGBUILD | 6 +- shared/utils.cpp | 4 +- translations/squawk.ru.ts | 8 ++ ui/squawk.cpp | 9 +- ui/squawk.h | 1 + ui/squawk.ui | 12 ++- ui/widgets/conversation.cpp | 91 ++++++++++++++++--- ui/widgets/conversation.h | 6 ++ ui/widgets/conversation.ui | 27 ++++-- ui/widgets/vcard/vcard.cpp | 6 +- 18 files changed, 162 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e528bd5..c328fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Squawk 0.1.4 (UNRELEASED) +## Squawk 0.1.4 (Apr 14, 2020) ### New features - message line now is in the same window with roster (new window dialog is still able to opened on context menu) - several new ways to manage your account password: @@ -8,6 +8,7 @@ - store it in config jammed (local hashing with the constant seed, not secure at all but might look like it is) - ask the account password on each program launch - store it in KWallet which is dynamically loaded +- dragging into conversation now attach files ### Bug fixes - never updating MUC avatars now get updated @@ -15,6 +16,7 @@ - statuses now behave better: they wrap if they don't fit, you can select them, you can follow links from there - messages and statuses don't loose content if you use < ore > symbols - now avatars of those who are not in the MUC right now but was also display next to the message +- fix crash on attempt to attach the same file for the second time ## Squawk 0.1.3 (Mar 31, 2020) diff --git a/CMakeLists.txt b/CMakeLists.txt index 20ae092..771481f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,9 +109,9 @@ add_dependencies(${CMAKE_PROJECT_NAME} translations) # Install the executable install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n) -install(FILES squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) -install(FILES squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png) -install(FILES squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png) -install(FILES squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png) -install(FILES squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png) -install(FILES squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) diff --git a/README.md b/README.md index 7d55eb5..30c6473 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![AUR version](https://img.shields.io/aur/version/squawk?style=flat-square)](https://aur.archlinux.org/packages/squawk/) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/macaw.me?logo=liberapay&style=flat-square)](https://liberapay.com/macaw.me) -![Squawk screenshot](https://macaw.me/images/squawk/0.1.3.png) +![Squawk screenshot](https://macaw.me/images/squawk/0.1.4.png) ### Prerequisites @@ -13,6 +13,7 @@ - lmdb - CMake 3.0 or higher - qxmpp 1.1.0 or higher +- kwallet (optional) ### Getting diff --git a/core/passwordStorageEngines/kwallet.cpp b/core/passwordStorageEngines/kwallet.cpp index fe05a2c..0dfe071 100644 --- a/core/passwordStorageEngines/kwallet.cpp +++ b/core/passwordStorageEngines/kwallet.cpp @@ -41,11 +41,6 @@ Core::PSE::KWallet::KWallet(): if (sState == initial) { lib.load(); - if (!lib.isLoaded()) { //fallback from the build directory - lib.setFileName("./core/passwordStorageEngines/libkwalletWrapper.so"); - lib.load(); - } - if (lib.isLoaded()) { openWallet = (OpenWallet) lib.resolve("openWallet"); networkWallet = (NetworkWallet) lib.resolve("networkWallet"); @@ -81,7 +76,9 @@ void Core::PSE::KWallet::open() { if (sState == success) { if (cState == disconnected) { - wallet = openWallet(networkWallet(), 0, ::KWallet::Wallet::Asynchronous); + QString name; + networkWallet(name); + wallet = openWallet(name, 0, ::KWallet::Wallet::Asynchronous); if (wallet) { cState = connecting; connect(wallet, SIGNAL(walletOpened(bool)), this, SLOT(onWalletOpened(bool))); @@ -89,6 +86,7 @@ void Core::PSE::KWallet::open() } else { everError = true; emit opened(false); + rejectPending(); } } } diff --git a/core/passwordStorageEngines/kwallet.h b/core/passwordStorageEngines/kwallet.h index 8ac52d2..28475d2 100644 --- a/core/passwordStorageEngines/kwallet.h +++ b/core/passwordStorageEngines/kwallet.h @@ -79,7 +79,7 @@ private: private: typedef ::KWallet::Wallet* (*OpenWallet)(const QString &, WId, ::KWallet::Wallet::OpenType); - typedef const char* (*NetworkWallet)(); + typedef void (*NetworkWallet)(QString&); typedef void (*DeleteWallet)(::KWallet::Wallet*); typedef int (*ReadPassword)(::KWallet::Wallet*, const QString&, QString&); typedef int (*WritePassword)(::KWallet::Wallet*, const QString&, const QString&); diff --git a/core/passwordStorageEngines/wrappers/kwallet.cpp b/core/passwordStorageEngines/wrappers/kwallet.cpp index 39a2eaf..f5e7cb5 100644 --- a/core/passwordStorageEngines/wrappers/kwallet.cpp +++ b/core/passwordStorageEngines/wrappers/kwallet.cpp @@ -8,8 +8,8 @@ extern "C" void deleteWallet(KWallet::Wallet* w) { w->deleteLater(); } -extern "C" const char* networkWallet() { - return KWallet::Wallet::NetworkWallet().toStdString().c_str(); +extern "C" void networkWallet(QString& str) { + str = KWallet::Wallet::NetworkWallet(); } extern "C" int readPassword(KWallet::Wallet* w, const QString &key, QString &value) { diff --git a/main.cpp b/main.cpp index b8be72c..d272d3d 100644 --- a/main.cpp +++ b/main.cpp @@ -42,7 +42,7 @@ int main(int argc, char *argv[]) QApplication::setApplicationName("squawk"); QApplication::setApplicationDisplayName("Squawk"); - QApplication::setApplicationVersion("0.1.3"); + QApplication::setApplicationVersion("0.1.4"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); diff --git a/order.h b/order.h index 1821e15..fa9379b 100644 --- a/order.h +++ b/order.h @@ -29,7 +29,7 @@ namespace W template > class Order { - + public: class Duplicates: public Utils::Exception { diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 400bc8c..9f8ef73 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Yury Gubich pkgname=squawk -pkgver=0.1.3 +pkgver=0.1.4 pkgrel=1 pkgdesc="An XMPP desktop messenger, written on pure c++ (qt)" arch=('i686' 'x86_64') @@ -8,8 +8,10 @@ url="https://git.macaw.me/blue/squawk" license=('GPL3') depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.1.0') makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools') +optdepends=('kwallet: secure password storage (requires rebuild)') + source=("$pkgname-$pkgver.tar.gz") -sha256sums=('adb172bb7d5b81bd9b83b192481a79ac985877e81604f401b3f2a08613b359bc') +sha256sums=('3b290381eaf15a35d24a58a36c29eee375a4ea77b606124982a063d7ecf98870') build() { cd "$srcdir/squawk" cmake . -D CMAKE_INSTALL_PREFIX=/usr -D CMAKE_BUILD_TYPE=Release diff --git a/shared/utils.cpp b/shared/utils.cpp index 06247f8..924be85 100644 --- a/shared/utils.cpp +++ b/shared/utils.cpp @@ -33,9 +33,9 @@ static const QRegularExpression urlReg("(? + + Drop files here to attach them to your message + Бросьте файлы сюда для того что бы прикрепить их к сообщению +
Global @@ -637,6 +641,10 @@ to be displayed as %1 Password for account %1 Пароль для учетной записи %1 + + Please select a contact to start chatting + Выберите контакт или группу что бы начать переписку + VCard diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 3e1eb9e..a808dff 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -34,7 +34,8 @@ Squawk::Squawk(QWidget *parent) : requestedAccountsForPasswords(), prompt(0), currentConversation(0), - restoreSelection() + restoreSelection(), + needToRestore(false) { m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); @@ -1139,6 +1140,7 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn if (id != 0) { delete id; } + needToRestore = true; restoreSelection = previous; return; } @@ -1204,5 +1206,8 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn void Squawk::onContextAboutToHide() { - m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect); + if (needToRestore) { + needToRestore = false; + m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect); + } } diff --git a/ui/squawk.h b/ui/squawk.h index 28a2f17..a6a27c0 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -126,6 +126,7 @@ private: QInputDialog* prompt; Conversation* currentConversation; QModelIndex restoreSelection; + bool needToRestore; protected: void closeEvent(QCloseEvent * event) override; diff --git a/ui/squawk.ui b/ui/squawk.ui index 19fdc4f..f6cb300 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -129,8 +129,18 @@ + + + 26 + 75 + true + + - <html><head/><body><p><span style=" font-size:26pt;">Please select a contact to start chatting</span></p></body></html> + Please select a contact to start chatting + + + Qt::PlainText Qt::AlignCenter diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index e677bc8..ec772a6 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -44,6 +44,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c statusIcon(0), statusLabel(0), filesLayout(0), + overlay(new QWidget()), filesToAttach(), scroll(down), manualSliderChange(false), @@ -92,15 +93,26 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c line->setMyAvatarPath(acc->getAvatarPath()); line->setMyName(acc->getName()); - QFont nf = m_ui->nameLabel->font(); - nf.setBold(true); - nf.setPointSize(nf.pointSize() + 2); - m_ui->nameLabel->setFont(nf); - - QFont sf = statusLabel->font(); - sf.setItalic(true); - sf.setPointSize(sf.pointSize() - 2); - statusLabel->setFont(sf); + QGridLayout* gr = static_cast(layout()); + QLabel* progressLabel = new QLabel(tr("Drop files here to attach them to your message")); + gr->addWidget(overlay, 0, 0, 2, 1); + QVBoxLayout* nl = new QVBoxLayout(); + QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect(); + opacity->setOpacity(0.8); + overlay->setLayout(nl); + overlay->setBackgroundRole(QPalette::Base); + overlay->setAutoFillBackground(true); + overlay->setGraphicsEffect(opacity); + progressLabel->setAlignment(Qt::AlignCenter); + QFont pf = progressLabel->font(); + pf.setBold(true); + pf.setPointSize(26); + progressLabel->setWordWrap(true); + progressLabel->setFont(pf); + nl->addStretch(); + nl->addWidget(progressLabel); + nl->addStretch(); + overlay->hide(); applyVisualEffects(); } @@ -362,10 +374,16 @@ void Conversation::addAttachedFile(const QString& path) Badge* badge = new Badge(path, info.fileName(), QIcon::fromTheme(type.iconName())); connect(badge, &Badge::close, this, &Conversation::onBadgeClose); - filesToAttach.push_back(badge); //TODO neet to check if there are any duplicated ids - filesLayout->addWidget(badge); - if (filesLayout->count() == 1) { - filesLayout->setContentsMargins(3, 3, 3, 3); + try { + filesToAttach.push_back(badge); + filesLayout->addWidget(badge); + if (filesLayout->count() == 1) { + filesLayout->setContentsMargins(3, 3, 3, 3); + } + } catch (const W::Order::Duplicates& e) { + delete badge; + } catch (...) { + throw; } } @@ -421,6 +439,53 @@ void Conversation::setFeedFrames(bool top, bool right, bool bottom, bool left) static_cast(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left); } +void Conversation::dragEnterEvent(QDragEnterEvent* event) +{ + bool accept = false; + if (event->mimeData()->hasUrls()) { + QList list = event->mimeData()->urls(); + for (const QUrl& url : list) { + if (url.isLocalFile()) { + QFileInfo info(url.toLocalFile()); + if (info.isReadable() && info.isFile()) { + accept = true; + break; + } + } + } + } + if (accept) { + event->acceptProposedAction(); + overlay->show(); + } +} + +void Conversation::dragLeaveEvent(QDragLeaveEvent* event) +{ + overlay->hide(); +} + +void Conversation::dropEvent(QDropEvent* event) +{ + bool accept = false; + if (event->mimeData()->hasUrls()) { + QList list = event->mimeData()->urls(); + for (const QUrl& url : list) { + if (url.isLocalFile()) { + QFileInfo info(url.toLocalFile()); + if (info.isReadable() && info.isFile()) { + addAttachedFile(info.canonicalFilePath()); + accept = true; + } + } + } + } + if (accept) { + event->acceptProposedAction(); + } + overlay->hide(); +} + bool VisibilityCatcher::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Show) { diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index f64ae54..27ce074 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "shared/message.h" #include "order.h" @@ -105,6 +107,9 @@ protected: void addAttachedFile(const QString& path); void removeAttachedFile(Badge* badge); void clearAttachedFiles(); + void dragEnterEvent(QDragEnterEvent* event) override; + void dragLeaveEvent(QDragLeaveEvent* event) override; + void dropEvent(QDropEvent* event) override; protected slots: void onEnterPressed(); @@ -138,6 +143,7 @@ protected: QLabel* statusIcon; QLabel* statusLabel; FlowLayout* filesLayout; + QWidget* overlay; W::Order filesToAttach; Scroll scroll; bool manualSliderChange; diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index d73875d..7093bcb 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -16,10 +16,10 @@ 0 - - - 0 - + + true + + 0 @@ -32,7 +32,7 @@ 0 - + @@ -115,6 +115,13 @@ + + + 12 + 75 + true + + @@ -128,6 +135,12 @@ 0 + + + 8 + true + + @@ -230,7 +243,7 @@ 0 0 520 - 392 + 385 @@ -258,7 +271,7 @@ widget_3 - + diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp index e3aa1b9..f1cfe3b 100644 --- a/ui/widgets/vcard/vcard.cpp +++ b/ui/widgets/vcard/vcard.cpp @@ -126,7 +126,11 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): overlay->setAutoFillBackground(true); overlay->setGraphicsEffect(opacity); progressLabel->setAlignment(Qt::AlignCenter); - progressLabel->setStyleSheet("font: 16pt"); + QFont pf = progressLabel->font(); + pf.setBold(true); + pf.setPointSize(26); + progressLabel->setFont(pf); + progressLabel->setWordWrap(true); nl->addStretch(); nl->addWidget(progress); nl->addWidget(progressLabel);