Merge pull request 'safePasswords' (#36) from safePasswords into master

This commit is contained in:
Blue 2020-04-14 16:32:28 +00:00
commit bdca0aa6b1
56 changed files with 2580 additions and 455 deletions

76
CHANGELOG.md Normal file
View File

@ -0,0 +1,76 @@
# Changelog
## 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:
- 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
- dragging into conversation now attach files
### Bug fixes
- 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
- 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)
### 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

View File

@ -62,18 +62,44 @@ 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)
add_subdirectory(external/simpleCrypt)
target_link_libraries(squawk squawkUI)
target_link_libraries(squawk squawkCORE)
target_link_libraries(squawk uuid)
@ -83,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)

View File

@ -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
@ -60,6 +61,13 @@ $ 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`)
## License
This project is licensed under the GPLv3 License - see the [LICENSE.md](LICENSE.md) file for details

View File

@ -20,16 +20,17 @@ set(squawkCORE_SRC
adapterFuctions.cpp
)
add_subdirectory(passwordStorageEngines)
# Tell CMake to create the helloworld executable
add_library(squawkCORE ${squawkCORE_SRC})
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)
@ -37,3 +38,7 @@ 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)
if (WITH_KWALLET)
target_link_libraries(squawkCORE kwalletPSE)
endif()

View File

@ -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<QString, Contact*>::const_iterator itr = contacts.find(bareJid);
@ -1052,6 +1063,7 @@ void Core::Account::onContactHistoryResponse(const std::list<Shared::Message>& l
void Core::Account::onClientError(QXmppClient::Error err)
{
qDebug() << "Error";
QString errorText;
QString errorType;
switch (err) {
@ -1129,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";
@ -1356,20 +1371,21 @@ 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;
bool hasAvatar = conf->readAvatarInfo(info);
if (hasAvatar) {
if (info.autogenerated) {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::valid));
cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::autocreated));
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::valid));
}
cData.insert("avatarPath", conf->avatarPath() + "." + info.type);
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
cData.insert("avatarState", QVariant::fromValue(Shared::Avatar::empty));
cData.insert("avatarPath", "");
requestVCard(jid);
}

View File

@ -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<QString, QString> pendingStateMessages;
Shared::AccountPassword passwordType;
private slots:
void onClientConnected();

View File

@ -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<QString, AvatarInfo>& 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) {

View File

@ -56,9 +56,10 @@ public:
std::list<Shared::Message> 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<QString, AvatarInfo>& data) const;
public:
const QString jid;

View File

@ -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()
@ -134,17 +137,20 @@ void Core::Conference::onRoomParticipantAdded(const QString& p_name)
{
QStringList comps = p_name.split("/");
QString resource = comps.back();
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);
QXmppMucItem mi = pres.mucItem();
if (resource == jid) {
resource = "";
}
std::map<QString, Archive::AvatarInfo>::const_iterator itr = exParticipants.find(resource);
bool hasAvatar = itr != exParticipants.end();
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<QString, QVariant> cData = {
{"lastActivity", lastInteraction},
@ -155,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<uint>(Shared::Avatar::valid));
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::autocreated));
}
cData.insert("avatarPath", avatarPath(resource) + "." + info.type);
cData.insert("avatarPath", avatarPath(resource) + "." + itr->second.type);
} else {
cData.insert("avatarState", static_cast<uint>(Shared::Avatar::empty));
cData.insert("avatarPath", "");
@ -169,22 +175,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 || !itr->second.autogenerated) {
setAutoGeneratedAvatar(resource);
}
}
break;
case QXmppPresence::VCardUpdateValidPhoto:{ //there is a photo, need to load
if (hasAvatar) {
if (itr->second.autogenerated || itr->second.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);
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},
@ -261,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<QString, Archive::AvatarInfo>::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<uint>(Shared::Avatar::autocreated)},
{"availability", 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<QString, Archive::AvatarInfo>::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<uint>(Shared::Avatar::autocreated)},
{"avatarPath", avatarPath(resource) + "." + ext}
{"avatarPath", avatarPath(resource) + "." + info.type}
});
} else {
std::map<QString, Archive::AvatarInfo>::iterator itr = exParticipants.find(resource);
if (itr != exParticipants.end()) {
exParticipants.erase(itr);
}
emit changeParticipant(resource, {
{"avatarState", static_cast<uint>(Shared::Avatar::empty)},
{"avatarPath", ""}
@ -309,3 +352,12 @@ Shared::VCard Core::Conference::handleResponseVCard(const QXmppVCardIq& card, co
return result;
}
QMap<QString, QVariant> Core::Conference::getAllAvatars() const
{
QMap<QString, QVariant> result;
for (const std::pair<QString, Archive::AvatarInfo>& pair : exParticipants) {
result.insert(pair.first, avatarPath(pair.first) + "." + pair.second.type);
}
return result;
}

View File

@ -19,9 +19,15 @@
#ifndef CORE_CONFERENCE_H
#define CORE_CONFERENCE_H
#include "rosteritem.h"
#include <QDir>
#include <QXmppMucManager.h>
#include <set>
#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<QString, QVariant> getAllAvatars() const;
signals:
void nickChanged(const QString& nick);
@ -58,11 +64,16 @@ signals:
void changeParticipant(const QString& name, const QMap<QString, QVariant>& 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<QString, Archive::AvatarInfo> exParticipants;
static const std::set<QString> supportedList;
private slots:
void onRoomJoined();

View File

@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.0)
project(pse)
if (WITH_KWALLET)
set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui 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})
endif()

View File

@ -0,0 +1,230 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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("kwalletWrapper");
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) {
QString name;
networkWallet(name);
wallet = openWallet(name, 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);
rejectPending();
}
}
}
}
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<QString, QString>::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<QString>::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<QString, QString>::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<QString>::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();
}
}

View File

@ -0,0 +1,112 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_PSE_KWALLET_H
#define CORE_PSE_KWALLET_H
#include <QObject>
#include <QLibrary>
#include <QDebug>
#include <QCoreApplication>
#include <map>
#include <set>
#include <KF5/KWallet/KWallet>
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 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&);
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<QString> readRequest;
std::map<QString, QString> writeRequest;
};
}}
#endif // CORE_PSE_KWALLET_H

View File

@ -0,0 +1,33 @@
#include <KF5/KWallet/KWallet>
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" void networkWallet(QString& str) {
str = KWallet::Wallet::NetworkWallet();
}
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);
}

View File

@ -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;
@ -501,7 +508,6 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co
path = avatarPath(resource) + ".png";
}
vCard.setAvatarType(type);
vCard.setAvatarPath(path);

View File

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

View File

@ -26,13 +26,27 @@ Core::Squawk::Squawk(QObject* parent):
QObject(parent),
accounts(),
amap(),
network()
network(),
waitingForAccounts(0)
#ifdef WITH_KWALLET
,kwallet()
#endif
{
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);
#ifdef WITH_KWALLET
if (kwallet.supportState() == PSE::KWallet::success) {
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()
@ -44,6 +58,11 @@ Core::Squawk::~Squawk()
}
}
void Core::Squawk::onWalletOpened(bool success)
{
qDebug() << "KWallet opened: " << success;
}
void Core::Squawk::stop()
{
qDebug("Stopping squawk core..");
@ -51,14 +70,31 @@ void Core::Squawk::stop()
QSettings settings;
settings.beginGroup("core");
settings.beginWriteArray("accounts");
SimpleCrypt crypto(passwordHash);
for (std::deque<Account*>::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<int>(ap));
}
settings.endArray();
settings.endGroup();
@ -72,21 +108,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 +120,17 @@ void Core::Squawk::newAccountRequest(const QMap<QString, QVariant>& 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 +138,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 +149,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<const QString&, const QString&>(&Account::removeContact), this, qOverload<const QString&, const QString&>(&Squawk::onAccountRemoveContact));
connect(acc, qOverload<const QString&>(&Account::removeContact), this, qOverload<const QString&>(&Squawk::onAccountRemoveContact));
connect(acc, qOverload<const QString&, const QString&>(&Account::removeContact),
this, qOverload<const QString&, const QString&>(&Squawk::onAccountRemoveContact));
connect(acc, qOverload<const QString&>(&Account::removeContact),
this, qOverload<const QString&>(&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 +181,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);
@ -192,7 +225,8 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
Account* acc = static_cast<Account*>(sender());
emit changeAccount(acc->getName(), {{"state", QVariant::fromValue(p_state)}});
if (p_state == Shared::ConnectionState::disconnected) {
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) {
@ -204,6 +238,17 @@ void Core::Squawk::onAccountConnectionStateChanged(Shared::ConnectionState p_sta
emit stateChanged(state);
}
}
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;
}
}
void Core::Squawk::onAccountAddContact(const QString& jid, const QString& group, const QMap<QString, QVariant>& data)
@ -320,12 +365,37 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
Core::Account* acc = itr->second;
Shared::ConnectionState st = acc->getState();
QMap<QString, QVariant>::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<QString, QVariant>::const_iterator mItr;
mItr = map.find("login");
if (mItr != map.end()) {
acc->setLogin(mItr->toString());
@ -346,6 +416,20 @@ void Core::Squawk::modifyAccountRequest(const QString& name, const QMap<QString,
acc->setServer(mItr->toString());
}
mItr = map.find("passwordType");
if (mItr != map.end()) {
acc->setPasswordType(Shared::Global::fromInt<Shared::AccountPassword>(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);
}
@ -486,8 +570,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<QString, QVariant>& data)
{
Account* acc = static_cast<Account*>(sender());
@ -574,3 +656,99 @@ 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;
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<Shared::AccountPassword>(settings.value("passwordType", static_cast<int>(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;
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;
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
}
}
}
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();
}

View File

@ -32,6 +32,11 @@
#include "shared/message.h"
#include "shared/global.h"
#include "networkaccess.h"
#include "external/simpleCrypt/simplecrypt.h"
#ifdef WITH_KWALLET
#include "passwordStorageEngines/kwallet.h"
#endif
namespace Core
{
@ -45,6 +50,7 @@ public:
signals:
void quit();
void ready();
void newAccount(const QMap<QString, QVariant>&);
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
void removeAccount(const QString& account);
@ -72,6 +78,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<QString, QVariant>& data);
void requestPassword(const QString& account);
public slots:
void start();
@ -100,6 +107,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<Account*> Accounts;
@ -109,11 +117,22 @@ private:
AccountsMap amap;
Shared::Availability state;
NetworkAccess network;
uint8_t waitingForAccounts;
private:
void addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource);
#ifdef WITH_KWALLET
PSE::KWallet kwallet;
#endif
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<QString, QVariant>& data);
@ -135,6 +154,24 @@ private slots:
void onAccountChangeRoomPresence(const QString& jid, const QString& nick, const QMap<QString, QVariant>& data);
void onAccountRemoveRoomPresence(const QString& jid, const QString& nick);
void onAccountChangeMessage(const QString& jid, const QString& id, const QMap<QString, QVariant>& data);
void onWalletOpened(bool success);
void onWalletResponsePassword(const QString& login, const QString& password);
void onWalletRejectPassword(const QString& login);
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
);
static const quint64 passwordHash = 0x08d054225ac4871d;
};
}

16
external/simpleCrypt/CMakeLists.txt vendored Normal file
View File

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

252
external/simpleCrypt/simplecrypt.cpp vendored Normal file
View File

@ -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 <QByteArray>
#include <QtDebug>
#include <QtGlobal>
#include <QDateTime>
#include <QCryptographicHash>
#include <QDataStream>
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<char>(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;
}

225
external/simpleCrypt/simplecrypt.h vendored Normal file
View File

@ -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 <QString>
#include <QVector>
#include <QFlags>
/**
@ 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<char> m_keyParts;
CompressionMode m_compressionMode;
IntegrityProtectionMode m_protectionMode;
Error m_lastError;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SimpleCrypt::CryptoFlags)
#endif // SimpleCrypt_H

View File

@ -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));
@ -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,9 +147,10 @@ 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();
w.readSettings();
int result = app.exec();

View File

@ -29,7 +29,7 @@ namespace W
template <typename data_type, typename comparator = std::less<data_type>>
class Order
{
public:
class Duplicates:
public Utils::Exception
{

View File

@ -1,6 +1,6 @@
# Maintainer: Yury Gubich <blue@macaw.me>
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

View File

@ -35,8 +35,8 @@ enum class ConnectionState {
};
Q_ENUM_NS(ConnectionState)
static const std::deque<QString> 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<QString> availabilityThemeIcons = {
"user-online",
"user-away",
@ -59,7 +59,6 @@ static const std::deque<QString> availabilityThemeIcons = {
"user-invisible",
"user-offline"
};
static const std::deque<QString> 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<QString> subscriptionStateThemeIcons = {"edit-none", "arrow-down-double", "arrow-up-double", "dialog-ok", "question"};
static const std::deque<QString> 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<QString> 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<QString> 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<QString> messageStateNames = {"Pending", "Sent", "Delivered", "Error"};
static const std::deque<QString> messageStateThemeIcons = {"state-offline", "state-sync", "state-ok", "state-error"};
enum class AccountPassword {
plain,
jammed,
alwaysAsk,
kwallet
};
Q_ENUM_NS(AccountPassword)
static const AccountPassword AccountPasswordHighest = AccountPassword::kwallet;
static const AccountPassword AccountPasswordLowest = AccountPassword::plain;
}
#endif // SHARED_ENUMS_H

View File

@ -21,50 +21,66 @@
#include "enums.h"
Shared::Global* Shared::Global::instance = 0;
const std::set<QString> Shared::Global::supportedImagesExts = {"png", "jpg", "webp", "jpeg", "gif", "svg"};
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", "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) {
@ -81,90 +97,78 @@ Shared::Global * Shared::Global::getInstance()
QString Shared::Global::getName(Message::State rl)
{
return instance->messageState[int(rl)];
return instance->messageState[static_cast<int>(rl)];
}
QString Shared::Global::getName(Shared::Affiliation af)
{
return instance->affiliation[int(af)];
return instance->affiliation[static_cast<int>(af)];
}
QString Shared::Global::getName(Shared::Availability av)
{
return instance->availability[int(av)];
return instance->availability[static_cast<int>(av)];
}
QString Shared::Global::getName(Shared::ConnectionState cs)
{
return instance->connectionState[int(cs)];
return instance->connectionState[static_cast<int>(cs)];
}
QString Shared::Global::getName(Shared::Role rl)
{
return instance->role[int(rl)];
return instance->role[static_cast<int>(rl)];
}
QString Shared::Global::getName(Shared::SubscriptionState ss)
{
return instance->subscriptionState[int(ss)];
return instance->subscriptionState[static_cast<int>(ss)];
}
template<>
Shared::Availability Shared::Global::fromInt(int src)
QString Shared::Global::getName(Shared::AccountPassword ap)
{
if (src < static_cast<int>(Shared::availabilityLowest) && src > static_cast<int>(Shared::availabilityHighest)) {
qDebug("An attempt to set invalid availability to Squawk core, skipping");
}
return static_cast<Shared::Availability>(src);
return instance->accountPassword[static_cast<int>(ap)];
}
template<>
Shared::Availability Shared::Global::fromInt(unsigned int src)
void Shared::Global::setSupported(const QString& pluginName, bool support)
{
if (src < static_cast<int>(Shared::availabilityLowest) && src > static_cast<int>(Shared::availabilityHighest)) {
qDebug("An attempt to set invalid availability to Squawk core, skipping");
std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
if (itr != instance->pluginSupport.end()) {
itr->second = support;
}
return static_cast<Shared::Availability>(src);
}
template<>
Shared::ConnectionState Shared::Global::fromInt(int src)
bool Shared::Global::supported(const QString& pluginName)
{
if (src < static_cast<int>(Shared::connectionStateLowest) && src > static_cast<int>(Shared::connectionStateHighest)) {
qDebug("An attempt to set invalid availability to Squawk core, skipping");
std::map<QString, bool>::iterator itr = instance->pluginSupport.find(pluginName);
if (itr != instance->pluginSupport.end()) {
return itr->second;
}
return static_cast<Shared::ConnectionState>(src);
return false;
}
template<>
Shared::ConnectionState Shared::Global::fromInt(unsigned int src)
QString Shared::Global::getDescription(Shared::AccountPassword ap)
{
if (src < static_cast<int>(Shared::connectionStateLowest) && src > static_cast<int>(Shared::connectionStateHighest)) {
qDebug("An attempt to set invalid availability to Squawk core, skipping");
}
return static_cast<Shared::ConnectionState>(src);
return instance->accountPasswordDescription[static_cast<int>(ap)];
}
template<>
Shared::SubscriptionState Shared::Global::fromInt(int src)
{
if (src < static_cast<int>(Shared::subscriptionStateLowest) && src > static_cast<int>(Shared::subscriptionStateHighest)) {
qDebug("An attempt to set invalid availability to Squawk core, skipping");
}
#define FROM_INT_INPL(Enum) \
template<> \
Enum Shared::Global::fromInt(int src) \
{ \
if (src < static_cast<int>(Enum##Lowest) && src > static_cast<int>(Enum##Highest)) { \
throw EnumOutOfRange(#Enum); \
} \
return static_cast<Enum>(src); \
} \
template<> \
Enum Shared::Global::fromInt(unsigned int src) {return fromInt<Enum>(static_cast<int>(src));}
return static_cast<Shared::SubscriptionState>(src);
}
template<>
Shared::SubscriptionState Shared::Global::fromInt(unsigned int src)
{
if (src < static_cast<int>(Shared::subscriptionStateLowest) && src > static_cast<int>(Shared::subscriptionStateHighest)) {
qDebug("An attempt to set invalid availability to Squawk core, skipping");
}
return static_cast<Shared::SubscriptionState>(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)

View File

@ -21,8 +21,11 @@
#include "enums.h"
#include "message.h"
#include "exception.h"
#include <map>
#include <set>
#include <deque>
#include <QCoreApplication>
#include <QDebug>
@ -42,6 +45,9 @@ namespace Shared {
static QString getName(Affiliation af);
static QString getName(Role rl);
static QString getName(Message::State rl);
static QString getName(AccountPassword ap);
static QString getDescription(AccountPassword ap);
const std::deque<QString> availability;
const std::deque<QString> connectionState;
@ -49,6 +55,14 @@ namespace Shared {
const std::deque<QString> affiliation;
const std::deque<QString> role;
const std::deque<QString> messageState;
const std::deque<QString> accountPassword;
const std::deque<QString> accountPasswordDescription;
static bool supported(const QString& pluginName);
static void setSupported(const QString& pluginName, bool support);
static const std::set<QString> supportedImagesExts;
template<typename T>
static T fromInt(int src);
@ -56,8 +70,23 @@ namespace Shared {
template<typename T>
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;
std::map<QString, bool> pluginSupport;
};
}

View File

@ -46,6 +46,8 @@ public:
delivered,
error
};
static const State StateHighest = State::error;
static const State StateLowest = State::pending;
Message(Type p_type);
Message();

View File

@ -27,3 +27,22 @@ QString Shared::generateUUID()
uuid_unparse_lower(uuid, uuid_str);
return uuid_str;
}
static const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
"(?:https?|ftp):\\/\\/"
"\\w+"
"(?:"
"[\\w\\.\\,\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]?"
"(?:"
"\\([\\w\\.\\,\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]+\\)"
")?"
")*"
")");
QString Shared::processMessageBody(const QString& msg)
{
QString processed = msg.toHtmlEscaped();
processed.replace(urlReg, "<a href=\"\\1\">\\1</a>");
return "<p style=\"white-space: pre-wrap;\">" + processed + "</p>";
}

View File

@ -21,6 +21,7 @@
#include <QString>
#include <QColor>
#include <QRegularExpression>
#include <uuid/uuid.h>
#include <vector>
@ -28,6 +29,7 @@
namespace Shared {
QString generateUUID();
QString processMessageBody(const QString& msg);
static const std::vector<QColor> colorPalette = {
QColor(244, 27, 63),

View File

@ -65,6 +65,10 @@
<translatorcomment>Ресурс по умолчанию</translatorcomment>
<translation>QXmpp</translation>
</message>
<message>
<source>Password storage</source>
<translation>Хранение пароля</translation>
</message>
</context>
<context>
<name>Accounts</name>
@ -115,161 +119,208 @@ p, li { white-space: pre-wrap; }
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation></translation>
</message>
<message>
<source>Drop files here to attach them to your message</source>
<translation>Бросьте файлы сюда для того что бы прикрепить их к сообщению</translation>
</message>
</context>
<context>
<name>Global</name>
<message>
<source>Disconnected</source>
<translation>Отключен</translation>
</message>
<message>
<source>Connecting</source>
<translation>Подключается</translation>
</message>
<message>
<source>Connected</source>
<translation>Подключен</translation>
</message>
<message>
<source>Error</source>
<translation>Ошибка</translation>
</message>
<message>
<source>Online</source>
<comment>Availability</comment>
<translation>В сети</translation>
</message>
<message>
<source>Away</source>
<comment>Availability</comment>
<translation>Отошел</translation>
</message>
<message>
<source>Busy</source>
<translation>Занят</translation>
</message>
<message>
<source>Absent</source>
<comment>Availability</comment>
<translation>Недоступен</translation>
</message>
<message>
<source>Busy</source>
<comment>Availability</comment>
<translation>Занят</translation>
</message>
<message>
<source>Chatty</source>
<comment>Availability</comment>
<translation>Готов поболтать</translation>
</message>
<message>
<source>Invisible</source>
<comment>Availability</comment>
<translation>Невидимый</translation>
</message>
<message>
<source>Offline</source>
<comment>Availability</comment>
<translation>Отключен</translation>
</message>
<message>
<source>Disconnected</source>
<comment>ConnectionState</comment>
<translation>Отключен</translation>
</message>
<message>
<source>Connecting</source>
<comment>ConnectionState</comment>
<translation>Подключается</translation>
</message>
<message>
<source>Connected</source>
<comment>ConnectionState</comment>
<translation>Подключен</translation>
</message>
<message>
<source>Error</source>
<comment>ConnectionState</comment>
<translation>Ошибка</translation>
</message>
<message>
<source>None</source>
<comment>SubscriptionState</comment>
<translation>Нет</translation>
</message>
<message>
<source>From</source>
<comment>SubscriptionState</comment>
<translation>Входящая</translation>
</message>
<message>
<source>To</source>
<comment>SubscriptionState</comment>
<translation>Исходящая</translation>
</message>
<message>
<source>Both</source>
<comment>SubscriptionState</comment>
<translation>Взаимная</translation>
</message>
<message>
<source>Unknown</source>
<comment>SubscriptionState</comment>
<translation>Неизвестно</translation>
</message>
<message>
<source>Unspecified</source>
<comment>Affiliation</comment>
<translation>Не назначено</translation>
</message>
<message>
<source>Outcast</source>
<comment>Affiliation</comment>
<translation>Изгой</translation>
</message>
<message>
<source>Nobody</source>
<comment>Affiliation</comment>
<translation>Никто</translation>
</message>
<message>
<source>Member</source>
<comment>Affiliation</comment>
<translation>Участник</translation>
</message>
<message>
<source>Admin</source>
<comment>Affiliation</comment>
<translation>Администратор</translation>
</message>
<message>
<source>Owner</source>
<comment>Affiliation</comment>
<translation>Владелец</translation>
</message>
<message>
<source>Unspecified</source>
<comment>Role</comment>
<translation>Не назначено</translation>
</message>
<message>
<source>Nobody</source>
<comment>Role</comment>
<translation>Никто</translation>
</message>
<message>
<source>Visitor</source>
<comment>Role</comment>
<translation>Гость</translation>
</message>
<message>
<source>Participant</source>
<comment>Role</comment>
<translation>Участник</translation>
</message>
<message>
<source>Moderator</source>
<comment>Role</comment>
<translation>Модератор</translation>
</message>
<message>
<source>Not specified</source>
<translation type="vanished">Не указан</translation>
</message>
<message>
<source>Personal</source>
<translation type="vanished">Личный</translation>
</message>
<message>
<source>Business</source>
<translation type="vanished">Рабочий</translation>
</message>
<message>
<source>Fax</source>
<translation type="vanished">Факс</translation>
</message>
<message>
<source>Pager</source>
<translation type="vanished">Пэйджер</translation>
</message>
<message>
<source>Voice</source>
<translation type="vanished">Стационарный</translation>
</message>
<message>
<source>Cell</source>
<translation type="vanished">Мобильный</translation>
</message>
<message>
<source>Video</source>
<translation type="vanished">Видеофон</translation>
</message>
<message>
<source>Modem</source>
<translation type="vanished">Модем</translation>
</message>
<message>
<source>Other</source>
<translation type="vanished">Другой</translation>
</message>
<message>
<source>Pending</source>
<translation>В процессе</translation>
<comment>MessageState</comment>
<translation>В процессе отправки</translation>
</message>
<message>
<source>Sent</source>
<comment>MessageState</comment>
<translation>Отправлено</translation>
</message>
<message>
<source>Delivered</source>
<comment>MessageState</comment>
<translation>Доставлено</translation>
</message>
<message>
<source>Error</source>
<comment>MessageState</comment>
<translation>Ошибка</translation>
</message>
<message>
<source>Plain</source>
<comment>AccountPassword</comment>
<translation>Открытый текст</translation>
</message>
<message>
<source>Jammed</source>
<comment>AccountPassword</comment>
<translation>Обфусцированный</translation>
</message>
<message>
<source>Always Ask</source>
<comment>AccountPassword</comment>
<translation>Всегда спрашивать</translation>
</message>
<message>
<source>KWallet</source>
<comment>AccountPassword</comment>
<translation>KWallet</translation>
</message>
<message>
<source>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&apos;s not</source>
<comment>AccountPasswordDescription</comment>
<translation>Ваш пароль будет храниться в обфусцированном виде в конфигурационном файле. Обфускация производится с помощью постоянного числа, которое можно найти в исходном коде программы. Это может и выглядит как шифрование но им не является</translation>
</message>
<message>
<source>Squawk is going to query you for the password on every start of the program</source>
<comment>AccountPasswordDescription</comment>
<translation>Squawk будет спрашивать пароль от этой учетной записи каждый раз при запуске</translation>
</message>
<message>
<source>Your password is going to be stored in config file in plain text</source>
<comment>AccountPasswordDescription</comment>
<translation>Ваш пароль будет храниться в конфигурационном файле открытым текстром</translation>
</message>
<message>
<source>Your password is going to be stored in KDE wallet storage (KWallet). You&apos;re going to be queried for permissions</source>
<comment>AccountPasswordDescription</comment>
<translation>Ваш пароль будет храниться в бумажнике KDE (KWallet). В первый раз программа попросит разрешения для доступа к бумажнику</translation>
</message>
</context>
<context>
<name>JoinConference</name>
@ -317,10 +368,6 @@ p, li { white-space: pre-wrap; }
</context>
<context>
<name>Message</name>
<message>
<source>Download</source>
<translation type="vanished">Скачать</translation>
</message>
<message>
<source>Open</source>
<translation>Открыть</translation>
@ -363,25 +410,6 @@ You can try again</source>
<translation>Загружается...</translation>
</message>
</context>
<context>
<name>Models::Accounts</name>
<message>
<source>Name</source>
<translation type="vanished">Имя</translation>
</message>
<message>
<source>Server</source>
<translation type="vanished">Сервер</translation>
</message>
<message>
<source>State</source>
<translation type="vanished">Состояние</translation>
</message>
<message>
<source>Error</source>
<translation type="vanished">Ошибка</translation>
</message>
</context>
<context>
<name>Models::Room</name>
<message>
@ -605,6 +633,18 @@ to be displayed as %1</source>
<source>Attached file</source>
<translation>Прикрепленный файл</translation>
</message>
<message>
<source>Input the password for account %1</source>
<translation>Введите пароль для учетной записи %1</translation>
</message>
<message>
<source>Password for account %1</source>
<translation>Пароль для учетной записи %1</translation>
</message>
<message>
<source>Please select a contact to start chatting</source>
<translation>Выберите контакт или группу что бы начать переписку</translation>
</message>
</context>
<context>
<name>VCard</name>

View File

@ -28,7 +28,8 @@ Models::Account::Account(const QMap<QString, QVariant>& 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<QString, QVariant>::const_iterator sItr = data.find("state");
if (sItr != data.end()) {
@ -38,6 +39,10 @@ Models::Account::Account(const QMap<QString, QVariant>& data, Models::Item* pare
if (aItr != data.end()) {
setAvailability(aItr.value().toUInt());
}
QMap<QString, QVariant>::const_iterator pItr = data.find("passwordType");
if (pItr != data.end()) {
setPasswordType(pItr.value().toUInt());
}
}
Models::Account::~Account()
@ -155,6 +160,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 +169,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 +192,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 +249,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<Shared::AccountPassword>(pt));
}

View File

@ -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 <QVariant>
#include <QIcon>
@ -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;

View File

@ -58,11 +58,11 @@ QVariant Models::Participant::data(int column) const
{
switch (column) {
case 4:
return static_cast<uint8_t>(affiliation);
return QVariant::fromValue(affiliation);
case 5:
return static_cast<uint8_t>(role);
return QVariant::fromValue(role);
case 6:
return static_cast<quint8>(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<uint8_t>(Shared::affiliationHighest)) {
Shared::Affiliation affil = static_cast<Shared::Affiliation>(aff);
setAffiliation(affil);
} else {
qDebug() << "An attempt to set wrong affiliation" << aff << "to the room participant" << name;
}
setAffiliation(Shared::Global::fromInt<Shared::Affiliation>(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<uint8_t>(Shared::roleHighest)) {
Shared::Role r = static_cast<Shared::Role>(p_role);
setRole(r);
} else {
qDebug() << "An attempt to set wrong role" << p_role << "to the room participant" << name;
}
setRole(Shared::Global::fromInt<Shared::Role>(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<quint8>(Shared::Avatar::valid)) {
Shared::Avatar state = static_cast<Shared::Avatar>(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<Shared::Avatar>(p_state));}

View File

@ -20,6 +20,7 @@
#define MODELS_PARTICIPANT_H
#include "abstractparticipant.h"
#include "shared/global.h"
namespace Models {

View File

@ -32,7 +32,8 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
avatarState(Shared::Avatar::empty),
avatarPath(""),
messages(),
participants()
participants(),
exParticipantAvatars()
{
QMap<QString, QVariant>::const_iterator itr = data.find("autoJoin");
if (itr != data.end()) {
@ -62,6 +63,15 @@ Models::Room::Room(const QString& p_jid, const QMap<QString, QVariant>& data, Mo
if (itr != data.end()) {
setAvatarPath(itr.value().toString());
}
itr = data.find("avatars");
if (itr != data.end()) {
QMap<QString, QVariant> avs = itr.value().toMap();
for (QMap<QString, QVariant>::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<QString, QVa
qDebug() << "An attempt to add already existing participant" << p_name << "to the room" << name << ", updating instead";
handleParticipantUpdate(itr, data);
} else {
std::map<QString, QString>::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<QString, QString> Models::Room::getExParticipantAvatars() const
{
return exParticipantAvatars;
}

View File

@ -75,6 +75,7 @@ public:
QString getAvatarPath() const;
std::map<QString, const Participant&> getParticipants() const;
QString getParticipantIconPath(const QString& name) const;
std::map<QString, QString> getExParticipantAvatars() const;
signals:
void participantJoined(const Participant& participant);
@ -99,6 +100,7 @@ private:
QString avatarPath;
Messages messages;
std::map<QString, Participant*> participants;
std::map<QString, QString> exParticipantAvatars;
};

View File

@ -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<int>& roles)
{
if (tl.column() == 0) {

View File

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

View File

@ -20,7 +20,6 @@
#include "ui_squawk.h"
#include <QDebug>
#include <QIcon>
#include <QInputDialog>
Squawk::Squawk(QWidget *parent) :
QMainWindow(parent),
@ -31,7 +30,12 @@ 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),
currentConversation(0),
restoreSelection(),
needToRestore(false)
{
m_ui->setupUi(this);
m_ui->roster->setModel(&rosterModel);
@ -41,7 +45,7 @@ Squawk::Squawk(QWidget *parent) :
m_ui->roster->header()->setStretchLastSection(false);
m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch);
for (int i = static_cast<int>(Shared::availabilityLowest); i < static_cast<int>(Shared::availabilityHighest) + 1; ++i) {
for (int i = static_cast<int>(Shared::AvailabilityLowest); i < static_cast<int>(Shared::AvailabilityHighest) + 1; ++i) {
Shared::Availability av = static_cast<Shared::Availability>(i);
m_ui->comboBox->addItem(Shared::availabilityIcon(av), Shared::Global::getName(av));
}
@ -51,17 +55,35 @@ 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<int>(&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"));
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();
if (settings.contains("splitter")) {
m_ui->splitter->restoreState(settings.value("splitter").toByteArray());
}
settings.endGroup();
}
Squawk::~Squawk() {
@ -71,7 +93,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);
@ -331,16 +353,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<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage),
this, qOverload<const Shared::Message&, const QString&>(&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) {
@ -359,6 +372,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item)
}
}
delete id;
}
}
}
@ -374,9 +388,8 @@ void Squawk::onConversationClosed(QObject* parent)
Conversation* conv = static_cast<Conversation*>(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<Room*>(conv);
@ -384,7 +397,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)
@ -416,6 +428,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);
}
}
}
}
@ -434,6 +449,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);
}
@ -453,6 +471,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);
@ -477,18 +498,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);
@ -499,14 +535,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<QString, QVariant>& 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);
}
}
@ -546,13 +594,37 @@ void Squawk::onConversationMessage(const Shared::Message& msg)
{
Conversation* conv = static_cast<Conversation*>(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<Conversation*>(sender());
Models::Roster::ElId id = conv->getId();
std::map<QString, std::set<Models::Roster::ElId>>::iterator itr = requestedFiles.insert(std::make_pair(msg.getId(), std::set<Models::Roster::ElId>())).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);
}
@ -567,6 +639,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);
@ -590,6 +666,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);
}
@ -748,7 +831,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);
}
});
@ -757,7 +842,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);
}
});
@ -871,14 +958,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();
@ -892,7 +971,6 @@ void Squawk::readSettings()
} // need to fix that
settings.endArray();
}
settings.endGroup();
}
@ -905,6 +983,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());
@ -958,3 +1038,176 @@ 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();
}
void Squawk::subscribeConversation(Conversation* conv)
{
connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed);
connect(conv, qOverload<const Shared::Message&>(&Conversation::sendMessage), this, qOverload<const Shared::Message&>(&Squawk::onConversationMessage));
connect(conv, qOverload<const Shared::Message&, const QString&>(&Conversation::sendMessage),
this, qOverload<const Shared::Message&, const QString&>(&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 (restoreSelection.isValid() && restoreSelection == current) {
restoreSelection = QModelIndex();
return;
}
if (current.isValid()) {
Models::Item* node = static_cast<Models::Item*>(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<Models::Contact*>(node);
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
break;
case Models::Item::presence:
contact = static_cast<Models::Contact*>(node->parentItem());
id = new Models::Roster::ElId(contact->getAccountName(), contact->getJid());
res = node->getName();
hasContext = false;
break;
case Models::Item::room:
room = static_cast<Models::Room*>(node);
id = new Models::Roster::ElId(room->getAccountName(), room->getJid());
break;
case Models::Item::participant:
room = static_cast<Models::Room*>(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;
}
needToRestore = true;
restoreSelection = previous;
return;
}
if (id != 0) {
if (currentConversation != 0) {
if (currentConversation->getId() == *id) {
if (contact != 0) {
currentConversation->setPalResource(res);
}
return;
} 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();
}
}
} else {
if (currentConversation != 0) {
currentConversation->deleteLater();
currentConversation = 0;
m_ui->filler->show();
}
}
}
void Squawk::onContextAboutToHide()
{
if (needToRestore) {
needToRestore = false;
m_ui->roster->selectionModel()->setCurrentIndex(restoreSelection, QItemSelectionModel::ClearAndSelect);
}
}

View File

@ -24,6 +24,7 @@
#include <QCloseEvent>
#include <QtDBus/QDBusInterface>
#include <QSettings>
#include <QInputDialog>
#include <deque>
#include <map>
@ -52,7 +53,6 @@ public:
explicit Squawk(QWidget *parent = nullptr);
~Squawk() override;
void readSettings();
void writeSettings();
signals:
@ -80,8 +80,10 @@ 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();
void newAccount(const QMap<QString, QVariant>& account);
void changeAccount(const QString& account, const QMap<QString, QVariant>& data);
void removeAccount(const QString& account);
@ -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<QString, QVariant>& data);
void requestPassword(const QString& account);
private:
typedef std::map<Models::Roster::ElId, Conversation*> Conversations;
@ -119,6 +122,11 @@ private:
QDBusInterface dbus;
std::map<QString, std::set<Models::Roster::ElId>> requestedFiles;
std::map<QString, VCard*> vCards;
std::deque<QString> requestedAccountsForPasswords;
QInputDialog* prompt;
Conversation* currentConversation;
QModelIndex restoreSelection;
bool needToRestore;
protected:
void closeEvent(QCloseEvent * event) override;
@ -146,7 +154,15 @@ 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();
void onRosterSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
void onContextAboutToHide();
private:
void checkNextAccountForPassword();
void onPasswordPromptDone();
void subscribeConversation(Conversation* conv);
};
#endif // SQUAWK_H

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>508</height>
<width>718</width>
<height>720</height>
</rect>
</property>
<property name="windowTitle">
@ -17,7 +17,7 @@
<enum>Qt::ToolButtonFollowStyle</enum>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@ -30,20 +30,50 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QComboBox" name="comboBox">
<property name="editable">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>1</number>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<property name="currentText">
<string/>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>-1</number>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="1" column="0">
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="2" column="1">
<widget class="QTreeView" name="roster">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
@ -51,6 +81,12 @@
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
@ -68,6 +104,56 @@
</attribute>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox">
<property name="editable">
<bool>false</bool>
</property>
<property name="currentText">
<string/>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="filler" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>26</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Please select a contact to start chatting</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
@ -75,8 +161,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>22</height>
<width>718</width>
<height>27</height>
</rect>
</property>
<widget class="QMenu" name="menuSettings">

View File

@ -23,18 +23,6 @@
#include <QFileInfo>
#include <QRegularExpression>
const QRegularExpression urlReg("(?<!<a\\shref=['\"])(?<!<img\\ssrc=['\"])("
"(?:https?|ftp):\\/\\/"
"\\w+"
"(?:"
"[\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]?"
"(?:"
"\\([\\w\\.\\/\\:\\;\\?\\&\\=\\@\\%\\#\\+\\-]+\\)"
")?"
")*"
")");
const QRegularExpression imgReg("((?:https?|ftp)://\\S+\\.(?:jpg|jpeg|png|svg|gif))");
Message::Message(const Shared::Message& source, bool p_outgoing, const QString& p_sender, const QString& avatarPath, QWidget* parent):
QWidget(parent),
outgoing(p_outgoing),
@ -66,12 +54,9 @@ Message::Message(const Shared::Message& source, bool p_outgoing, const QString&
body->setBackgroundRole(QPalette::AlternateBase);
body->setAutoFillBackground(true);
QString bd = msg.getBody();
//bd.replace(imgReg, "<img src=\"\\1\"/>");
bd.replace(urlReg, "<a href=\"\\1\">\\1</a>");
//bd.replace("\n", "<br>");
QString bd = Shared::processMessageBody(msg.getBody());
text->setTextFormat(Qt::RichText);
text->setText("<p style=\"white-space: pre-wrap;\">" + bd + "</p>");;
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<QString, QVariant>& data)
{
bool idChanged = msg.change(data);
QString bd = msg.getBody();
//bd.replace(imgReg, "<img src=\"\\1\"/>");
bd.replace(urlReg, "<a href=\"\\1\">\\1</a>");
QString body = msg.getBody();
QString bd = Shared::processMessageBody(body);
if (body.size() > 0) {
text->setText(bd);
if (bd.size() > 0) {
text->show();
} else {
text->setText(body);
text->hide();
}
if (msg.getEdited()) {

View File

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

View File

@ -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<QString, QString>::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<QString, QString>::iterator itr = palAvatars.find(jid);
if (itr == palAvatars.end()) {
palAvatars.insert(std::make_pair(jid, path));
std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(jid);
if (eitr != exPalAvatars.end()) {
exPalAvatars.erase(eitr);
}
} else {
itr->second = path;
}
@ -265,6 +276,12 @@ void MessageLine::dropPalAvatar(const QString& jid)
std::map<QString, QString>::iterator itr = palAvatars.find(jid);
if (itr != palAvatars.end()) {
palAvatars.erase(itr);
}
std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(jid);
if (eitr != exPalAvatars.end()) {
exPalAvatars.erase(eitr);
}
std::map<QString, Index>::iterator pItr = palMessages.find(jid);
if (pItr != palMessages.end()) {
@ -272,6 +289,20 @@ void MessageLine::dropPalAvatar(const QString& jid)
itr->second->setAvatarPath("");
}
}
}
void MessageLine::movePalAvatarToEx(const QString& name)
{
std::map<QString, QString>::iterator itr = palAvatars.find(name);
if (itr != palAvatars.end()) {
std::map<QString, QString>::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);
}
}
@ -427,6 +458,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 +473,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
@ -453,3 +490,19 @@ void MessageLine::setMyAvatarPath(const QString& p_path)
}
}
}
void MessageLine::setExPalAvatars(const std::map<QString, QString>& data)
{
exPalAvatars = data;
for (const std::pair<QString, Index>& pair : palMessages) {
if (palAvatars.find(pair.first) == palAvatars.end()) {
std::map<QString, QString>::const_iterator eitr = exPalAvatars.find(pair.first);
if (eitr != exPalAvatars.end()) {
for (const std::pair<QString, Message*>& mp : pair.second) {
mp.second->setAvatarPath(eitr->second);
}
}
}
}
}

View File

@ -53,11 +53,14 @@ 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);
void dropPalAvatar(const QString& jid);
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
void setExPalAvatars(const std::map<QString, QString>& data);
void movePalAvatarToEx(const QString& name);
signals:
void resize(int amount);
@ -89,6 +92,7 @@ private:
std::map<QString, Index> palMessages;
std::map<QString, QString> uploadPaths;
std::map<QString, QString> palAvatars;
std::map<QString, QString> exPalAvatars;
QVBoxLayout* layout;
QString myName;

View File

@ -19,10 +19,25 @@
#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);
connect(m_ui->passwordType, qOverload<int>(&QComboBox::currentIndexChanged), this, &Account::onComboboxChange);
for (int i = static_cast<int>(Shared::AccountPasswordLowest); i < static_cast<int>(Shared::AccountPasswordHighest) + 1; ++i) {
Shared::AccountPassword ap = static_cast<Shared::AccountPassword>(i);
m_ui->passwordType->addItem(Shared::Global::getName(ap));
}
m_ui->passwordType->setCurrentIndex(static_cast<int>(Shared::AccountPassword::plain));
if (!Shared::Global::supported("KWallet")) {
QStandardItemModel *model = static_cast<QStandardItemModel*>(m_ui->passwordType->model());
QStandardItem *item = model->item(static_cast<int>(Shared::AccountPassword::kwallet));
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
}
}
Account::~Account()
@ -37,6 +52,7 @@ QMap<QString, QVariant> 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 +69,11 @@ void Account::setData(const QMap<QString, QVariant>& 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());
}
void Account::onComboboxChange(int index)
{
QString description = Shared::Global::getDescription(Shared::Global::fromInt<Shared::AccountPassword>(index));
m_ui->comment->setText(description);
}

View File

@ -19,11 +19,14 @@
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include <QScopedPointer>
#include <QDialog>
#include <QScopedPointer>
#include <QMap>
#include <QString>
#include <QVariant>
#include <QStandardItemModel>
#include "shared/global.h"
namespace Ui
{
@ -42,6 +45,9 @@ public:
void setData(const QMap<QString, QVariant>& data);
void lockId();
private slots:
void onComboboxChange(int index);
private:
QScopedPointer<Ui::Account> m_ui;
};

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>395</width>
<height>272</height>
<width>438</width>
<height>342</height>
</rect>
</property>
<property name="windowTitle">
@ -114,14 +114,14 @@
</property>
</widget>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Resource</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="6" column="1">
<widget class="QLineEdit" name="resource">
<property name="toolTip">
<string>A resource name like &quot;Home&quot; or &quot;Work&quot;</string>
@ -131,6 +131,32 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Password storage</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="passwordType"/>
</item>
<item row="5" column="1">
<widget class="QLabel" name="comment">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>

View File

@ -22,6 +22,7 @@
#include <QDebug>
Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) :
QWidget(parent),
m_ui(new Ui::Accounts),
model(p_model),
editing(false),
@ -36,11 +37,12 @@ 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;
void Accounts::onAddButton(bool clicked)
void Accounts::onAddButton()
{
Account* acc = new Account();
connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted);
@ -70,7 +72,7 @@ void Accounts::onAccountRejected()
editing = false;
}
void Accounts::onEditButton(bool clicked)
void Accounts::onEditButton()
{
Account* acc = new Account();
@ -80,7 +82,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 +92,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 +134,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 +148,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();

View File

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

View File

@ -19,7 +19,6 @@
#include "conversation.h"
#include "ui_conversation.h"
#include "ui/utils/dropshadoweffect.h"
#include "shared/icons.h"
#include <QDebug>
#include <QScrollBar>
@ -45,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),
@ -93,6 +93,27 @@ Conversation::Conversation(bool muc, Models::Account* acc, const QString pJid, c
line->setMyAvatarPath(acc->getAvatarPath());
line->setMyName(acc->getName());
QGridLayout* gr = static_cast<QGridLayout*>(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();
}
@ -210,6 +231,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;
@ -303,7 +329,7 @@ void Conversation::onFileSelected()
void Conversation::setStatus(const QString& status)
{
statusLabel->setText(status);
statusLabel->setText(Shared::processMessageBody(status));
}
void Conversation::onScrollResize()
@ -334,6 +360,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;
@ -343,11 +374,17 @@ 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
try {
filesToAttach.push_back(badge);
filesLayout->addWidget(badge);
if (filesLayout->count() == 1) {
filesLayout->setContentsMargins(3, 3, 3, 3);
}
} catch (const W::Order<Badge*, Badge::Comparator>::Duplicates& e) {
delete badge;
} catch (...) {
throw;
}
}
void Conversation::removeAttachedFile(Badge* badge)
@ -397,6 +434,57 @@ 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<DropShadowEffect*>(m_ui->scrollArea->graphicsEffect())->setFrame(top, right, bottom, left);
}
void Conversation::dragEnterEvent(QDragEnterEvent* event)
{
bool accept = false;
if (event->mimeData()->hasUrls()) {
QList<QUrl> 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<QUrl> 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)
{

View File

@ -22,14 +22,19 @@
#include <QWidget>
#include <QScopedPointer>
#include <QMap>
#include <QMimeData>
#include <QFileInfo>
#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"
#include "ui/utils/badge.h"
#include "shared/icons.h"
#include "shared/utils.h"
namespace Ui
{
@ -72,6 +77,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 +88,8 @@ public:
void responseFileProgress(const QString& messageId, qreal progress);
virtual void setAvatar(const QString& path);
void changeMessage(const QString& id, const QMap<QString, QVariant>& 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);
@ -99,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();
@ -132,6 +143,7 @@ protected:
QLabel* statusIcon;
QLabel* statusLabel;
FlowLayout* filesLayout;
QWidget* overlay;
W::Order<Badge*, Badge::Comparator> filesToAttach;
Scroll scroll;
bool manualSliderChange;

View File

@ -10,10 +10,16 @@
<height>658</height>
</rect>
</property>
<layout class="QVBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
@ -26,7 +32,7 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<item row="0" column="0">
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
@ -109,6 +115,13 @@
</property>
<item>
<widget class="QLabel" name="nameLabel">
<property name="font">
<font>
<pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
@ -116,9 +129,30 @@
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
@ -128,9 +162,12 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<width>0</width>
<height>20</height>
</size>
</property>
@ -206,7 +243,7 @@
<x>0</x>
<y>0</y>
<width>520</width>
<height>389</height>
<height>385</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
@ -234,7 +271,7 @@
<zorder>widget_3</zorder>
</widget>
</item>
<item>
<item row="1" column="0">
<widget class="QWidget" name="widget_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">

View File

@ -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);
}

View File

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