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

Merge pull request #6073

Anthony Bilinski (1):
      fix(notification): hide snore warning log spam

Mick Sayson (2):
      feat(notification): Notification string generator for multiple messages
      feat(notification): Notifications now always replace the previous one
This commit is contained in:
Anthony Bilinski 2020-07-02 01:34:34 -07:00
commit 431fd7bfa2
No known key found for this signature in database
GPG Key ID: 2AA8E0DA1B31FB3C
17 changed files with 999 additions and 182 deletions

View File

@ -369,6 +369,8 @@ set(${PROJECT_NAME}_SOURCES
src/model/chathistory.cpp src/model/chathistory.cpp
src/model/toxclientstandards.h src/model/toxclientstandards.h
src/model/ibootstraplistgenerator.h src/model/ibootstraplistgenerator.h
src/model/notificationgenerator.h
src/model/notificationgenerator.cpp
src/net/bootstrapnodeupdater.cpp src/net/bootstrapnodeupdater.cpp
src/net/bootstrapnodeupdater.h src/net/bootstrapnodeupdater.h
src/net/avatarbroadcaster.cpp src/net/avatarbroadcaster.cpp

View File

@ -48,6 +48,7 @@ auto_test(model groupmessagedispatcher)
auto_test(model messageprocessor) auto_test(model messageprocessor)
auto_test(model sessionchatlog) auto_test(model sessionchatlog)
auto_test(model exiftransform) auto_test(model exiftransform)
auto_test(model notificationgenerator)
if (UNIX) if (UNIX)
auto_test(platform posixsignalnotifier) auto_test(platform posixsignalnotifier)

View File

@ -94,6 +94,16 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
&& msg == QString("QFSFileEngine::open: No file name specified")) && msg == QString("QFSFileEngine::open: No file name specified"))
return; return;
QRegExp snoreFilter{QStringLiteral("Snore::Notification.*was already closed")};
if (type == QtWarningMsg
&& msg.contains(snoreFilter))
{
// snorenotify logs this when we call requestCloseNotification correctly. The behaviour still works, so we'll
// just mask the warning for now. The issue has been reported upstream:
// https://github.com/qTox/qTox/pull/6073#pullrequestreview-420748519
return;
}
QString file = ctxt.file; QString file = ctxt.file;
// We're not using QT_MESSAGELOG_FILE here, because that can be 0, NULL, or // We're not using QT_MESSAGELOG_FILE here, because that can be 0, NULL, or
// nullptr in release builds. // nullptr in release builds.

View File

@ -0,0 +1,30 @@
/*
Copyright © 2020 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <QPixmap>
struct NotificationData
{
QString title;
QString message;
QPixmap pixmap;
};

View File

@ -0,0 +1,267 @@
/*
Copyright © 2020 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "notificationgenerator.h"
#include "src/chatlog/content/filetransferwidget.h"
#include <QCollator>
namespace
{
size_t getNumMessages(
const QHash<const Friend*, size_t>& friendNotifications,
const QHash<const Group* , size_t>& groupNotifications)
{
auto numMessages = std::accumulate(friendNotifications.begin(), friendNotifications.end(), 0);
numMessages = std::accumulate(groupNotifications.begin(), groupNotifications.end(), numMessages);
return numMessages;
}
size_t getNumChats(
const QHash<const Friend*, size_t>& friendNotifications,
const QHash<const Group* , size_t>& groupNotifications)
{
return friendNotifications.size() + groupNotifications.size();
}
QString generateMultiChatTitle(size_t numChats, size_t numMessages)
{
//: e.g. 3 messages from 2 chats
return QObject::tr("%1 message(s) from %2 chats")
.arg(numMessages)
.arg(numChats);
}
template <typename T>
QString generateSingleChatTitle(
const QHash<T, size_t> numNotifications,
T contact)
{
if (numNotifications[contact] > 1)
{
//: e.g. 2 messages from Bob
return QObject::tr("%1 message(s) from %2")
.arg(numNotifications[contact])
.arg(contact->getDisplayedName());
}
else
{
return contact->getDisplayedName();
}
}
QString generateTitle(
const QHash<const Friend*, size_t>& friendNotifications,
const QHash<const Group* , size_t>& groupNotifications,
const Friend* f)
{
auto numChats = getNumChats(friendNotifications, groupNotifications);
if (numChats > 1)
{
return generateMultiChatTitle(numChats, getNumMessages(friendNotifications, groupNotifications));
}
else
{
return generateSingleChatTitle(friendNotifications, f);
}
}
QString generateTitle(
const QHash<const Friend*, size_t>& friendNotifications,
const QHash<const Group* , size_t>& groupNotifications,
const Group* g)
{
auto numChats = getNumChats(friendNotifications, groupNotifications);
if (numChats > 1)
{
return generateMultiChatTitle(numChats, getNumMessages(friendNotifications, groupNotifications));
}
else
{
return generateSingleChatTitle(groupNotifications, g);
}
}
QString generateContent(
const QHash<const Friend*, size_t>& friendNotifications,
const QHash<const Group*, size_t>& groupNotifications,
QString lastMessage,
const ToxPk& sender)
{
assert(friendNotifications.size() > 0 || groupNotifications.size() > 0);
auto numChats = getNumChats(friendNotifications, groupNotifications);
if (numChats > 1) {
// Copy all names into a vector to simplify formatting logic between
// multiple lists
std::vector<QString> displayNames;
displayNames.reserve(numChats);
for (auto it = friendNotifications.begin(); it != friendNotifications.end(); ++it) {
displayNames.push_back(it.key()->getDisplayedName());
}
for (auto it = groupNotifications.begin(); it != groupNotifications.end(); ++it) {
displayNames.push_back(it.key()->getDisplayedName());
}
assert(displayNames.size() > 0);
// Lexiographically sort all display names to ensure consistent formatting
QCollator collator;
std::sort(displayNames.begin(), displayNames.end(), [&] (const QString& a, const QString& b) {
return collator.compare(a, b) < 1;
});
auto it = displayNames.begin();
QString ret = *it;
while (++it != displayNames.end()) {
ret += ", " + *it;
}
return ret;
}
else {
if (groupNotifications.size() == 1) {
return groupNotifications.begin().key()->getPeerList()[sender] + ": " + lastMessage;
}
return lastMessage;
}
}
QPixmap getSenderAvatar(Profile* profile, const ToxPk& sender)
{
return profile ? profile->loadAvatar(sender) : QPixmap();
}
} // namespace
NotificationGenerator::NotificationGenerator(
INotificationSettings const& notificationSettings,
Profile* profile)
: notificationSettings(notificationSettings)
, profile(profile)
{}
NotificationData NotificationGenerator::friendMessageNotification(const Friend* f, const QString& message)
{
friendNotifications[f]++;
NotificationData ret;
if (notificationSettings.getNotifyHide()) {
ret.title = tr("New message");
return ret;
}
ret.title = generateTitle(friendNotifications, groupNotifications, f);
ret.message = generateContent(friendNotifications, groupNotifications, message, f->getPublicKey());
ret.pixmap = getSenderAvatar(profile, f->getPublicKey());
return ret;
}
NotificationData NotificationGenerator::groupMessageNotification(const Group* g, const ToxPk& sender, const QString& message)
{
groupNotifications[g]++;
NotificationData ret;
if (notificationSettings.getNotifyHide()){
ret.title = tr("New group message");
return ret;
}
ret.title = generateTitle(friendNotifications, groupNotifications, g);
ret.message = generateContent(friendNotifications, groupNotifications, message, sender);
ret.pixmap = getSenderAvatar(profile, sender);
return ret;
}
NotificationData NotificationGenerator::fileTransferNotification(const Friend* f, const QString& filename, size_t fileSize)
{
friendNotifications[f]++;
NotificationData ret;
if (notificationSettings.getNotifyHide()) {
ret.title = tr("Incoming file transfer");
return ret;
}
auto numChats = getNumChats(friendNotifications, groupNotifications);
auto numMessages = getNumMessages(friendNotifications, groupNotifications);
if (numChats > 1 || numMessages > 1)
{
ret.title = generateTitle(friendNotifications, groupNotifications, f);
ret.message = generateContent(friendNotifications, groupNotifications, tr("Incoming file transfer"), f->getPublicKey());
}
else
{
//: e.g. Bob - file transfer
ret.title = tr("%1 - file transfer").arg(f->getDisplayedName());
ret.message = filename + " (" + FileTransferWidget::getHumanReadableSize(fileSize) + ")";
}
ret.pixmap = getSenderAvatar(profile, f->getPublicKey());
return ret;
}
NotificationData NotificationGenerator::groupInvitationNotification(const Friend* from)
{
NotificationData ret;
if (notificationSettings.getNotifyHide()) {
ret.title = tr("Group invite received");
return ret;
}
ret.title = tr("%1 invites you to join a group.").arg(from->getDisplayedName());
ret.message = "";
ret.pixmap = getSenderAvatar(profile, from->getPublicKey());
return ret;
}
NotificationData NotificationGenerator::friendRequestNotification(const ToxPk& sender, const QString& message)
{
NotificationData ret;
if (notificationSettings.getNotifyHide()) {
ret.title = tr("Friend request received");
return ret;
}
ret.title = tr("Friend request received from %1").arg(sender.toString());
ret.message = message;
return ret;
}
void NotificationGenerator::onNotificationActivated()
{
friendNotifications = {};
groupNotifications = {};
}

View File

@ -0,0 +1,57 @@
/*
Copyright © 2020 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "notificationdata.h"
#include "friend.h"
#include "group.h"
#include "src/persistence/inotificationsettings.h"
#include "src/persistence/profile.h"
#include <QObject>
#include <QHash>
class NotificationGenerator : public QObject
{
public:
NotificationGenerator(
INotificationSettings const& notificationSettings,
// Optional profile input to lookup avatars. Avatar lookup is not
// currently mockable so we allow profile to be nullptr for unit
// testing
Profile* profile);
NotificationData friendMessageNotification(const Friend* f, const QString& message);
NotificationData groupMessageNotification(const Group* g, const ToxPk& sender, const QString& message);
NotificationData fileTransferNotification(const Friend* f, const QString& filename, size_t fileSize);
NotificationData groupInvitationNotification(const Friend* from);
NotificationData friendRequestNotification(const ToxPk& sender, const QString& message);
public slots:
void onNotificationActivated();
private:
INotificationSettings const& notificationSettings;
Profile* profile;
QHash<const Friend*, size_t> friendNotifications;
QHash<const Group*, size_t> groupNotifications;
};

View File

@ -27,6 +27,4 @@ public:
virtual ~IGroupSettings() = default; virtual ~IGroupSettings() = default;
virtual QStringList getBlackList() const = 0; virtual QStringList getBlackList() const = 0;
virtual void setBlackList(const QStringList& blist) = 0; virtual void setBlackList(const QStringList& blist) = 0;
virtual bool getGroupAlwaysNotify() const = 0;
virtual void setGroupAlwaysNotify(bool newValue) = 0;
}; };

View File

@ -0,0 +1,47 @@
/*
Copyright © 2020 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QStringList>
class INotificationSettings
{
public:
virtual bool getNotify() const = 0;
virtual void setNotify(bool newValue) = 0;
virtual bool getShowWindow() const = 0;
virtual void setShowWindow(bool newValue) = 0;
virtual bool getDesktopNotify() const = 0;
virtual void setDesktopNotify(bool enabled) = 0;
virtual bool getNotifySound() const = 0;
virtual void setNotifySound(bool newValue) = 0;
virtual bool getNotifyHide() const = 0;
virtual void setNotifyHide(bool newValue) = 0;
virtual bool getBusySound() const = 0;
virtual void setBusySound(bool newValue) = 0;
virtual bool getGroupAlwaysNotify() const = 0;
virtual void setGroupAlwaysNotify(bool newValue) = 0;
};

View File

@ -27,6 +27,7 @@
#include "src/persistence/paths.h" #include "src/persistence/paths.h"
#include "src/persistence/ifriendsettings.h" #include "src/persistence/ifriendsettings.h"
#include "src/persistence/igroupsettings.h" #include "src/persistence/igroupsettings.h"
#include "src/persistence/inotificationsettings.h"
#include "src/video/ivideosettings.h" #include "src/video/ivideosettings.h"
#include <QDateTime> #include <QDateTime>
@ -50,7 +51,8 @@ class Settings : public QObject,
public IFriendSettings, public IFriendSettings,
public IGroupSettings, public IGroupSettings,
public IAudioSettings, public IAudioSettings,
public IVideoSettings public IVideoSettings,
public INotificationSettings
{ {
Q_OBJECT Q_OBJECT
@ -311,23 +313,23 @@ public:
bool getCheckUpdates() const; bool getCheckUpdates() const;
void setCheckUpdates(bool newValue); void setCheckUpdates(bool newValue);
bool getNotify() const; bool getNotify() const override;
void setNotify(bool newValue); void setNotify(bool newValue) override;
bool getShowWindow() const; bool getShowWindow() const override;
void setShowWindow(bool newValue); void setShowWindow(bool newValue) override;
bool getDesktopNotify() const; bool getDesktopNotify() const override;
void setDesktopNotify(bool enabled); void setDesktopNotify(bool enabled) override;
bool getNotifySound() const; bool getNotifySound() const override;
void setNotifySound(bool newValue); void setNotifySound(bool newValue) override;
bool getNotifyHide() const; bool getNotifyHide() const override;
void setNotifyHide(bool newValue); void setNotifyHide(bool newValue) override;
bool getBusySound() const; bool getBusySound() const override;
void setBusySound(bool newValue); void setBusySound(bool newValue) override;
bool getGroupAlwaysNotify() const override; bool getGroupAlwaysNotify() const override;
void setGroupAlwaysNotify(bool newValue) override; void setGroupAlwaysNotify(bool newValue) override;

View File

@ -25,6 +25,7 @@
#include <libsnore/snore.h> #include <libsnore/snore.h>
#include <QDebug> #include <QDebug>
#include <QThread>
DesktopNotify::DesktopNotify() DesktopNotify::DesktopNotify()
: notifyCore{Snore::SnoreCore::instance()} : notifyCore{Snore::SnoreCore::instance()}
@ -37,43 +38,52 @@ DesktopNotify::DesktopNotify()
snoreApp = Snore::Application("qTox", snoreIcon); snoreApp = Snore::Application("qTox", snoreIcon);
notifyCore.registerApplication(snoreApp); notifyCore.registerApplication(snoreApp);
connect(&notifyCore, &Snore::SnoreCore::notificationClosed, this, &DesktopNotify::onNotificationClose);
} }
void DesktopNotify::createNotification(const QString& title, const QString& text, Snore::Icon& icon) void DesktopNotify::notifyMessage(const NotificationData& notificationData)
{ {
const Settings& s = Settings::getInstance(); const Settings& s = Settings::getInstance();
if(!(s.getNotify() && s.getDesktopNotify())) { if(!(s.getNotify() && s.getDesktopNotify())) {
return; return;
} }
Snore::Notification notify{snoreApp, Snore::Alert(), title, text, icon}; auto icon = notificationData.pixmap.isNull() ? snoreIcon : Snore::Icon(notificationData.pixmap);
auto newNotification = Snore::Notification{snoreApp, Snore::Alert(), notificationData.title, notificationData.message, icon, 0};
latestId = newNotification.id();
notifyCore.broadcastNotification(notify); if (lastNotification.isValid()) {
} // Workaround for broken updating behavior in snore. Snore increments
// the message count when a notification is updated. Snore also caps the
void DesktopNotify::notifyMessage(const QString& title, const QString& message) // number of outgoing messages at 3. This means that if we update
{ // notifications more than 3 times we do not get notifications until the
createNotification(title, message, snoreIcon); // user activates the notification.
} //
// We work around this by closing the existing notification and replacing
void DesktopNotify::notifyMessagePixmap(const QString& title, const QString& message, QPixmap avatar) // it with a new one. We then only process the notification close if the
{ // latest notification id is the same as the one we are closing. This allows
Snore::Icon new_icon(avatar); // us to continue counting how many unread messages a user has until they
createNotification(title, message, new_icon); // close the notification themselves.
} //
// I've filed a bug on the snorenotify mailing list but the project seems
void DesktopNotify::notifyMessageSimple(const MessageType type) // pretty dead. I filed a ticket on March 11 2020, and as of April 5 2020
{ // the moderators have not even acknowledged the message. A previous message
QString message; // got a response starting with "Snorenotify isn't that well maintained any more"
switch (type) { // (see https://mail.kde.org/pipermail/snorenotify/2019-March/000004.html)
case MessageType::FRIEND: message = tr("New message"); break; // so I don't have hope of this being fixed any time soon
case MessageType::FRIEND_FILE: message = tr("Incoming file transfer"); break; notifyCore.requestCloseNotification(lastNotification, Snore::Notification::CloseReasons::Dismissed);
case MessageType::FRIEND_REQUEST: message = tr("Friend request received"); break;
case MessageType::GROUP: message = tr("New group message"); break;
case MessageType::GROUP_INVITE: message = tr("Group invite received"); break;
default: break;
} }
createNotification(message, {}, snoreIcon); notifyCore.broadcastNotification(newNotification);
lastNotification = newNotification;
}
void DesktopNotify::onNotificationClose(Snore::Notification notification)
{
if (notification.id() == latestId) {
lastNotification = {};
emit notificationClosed();
}
} }
#endif #endif

View File

@ -19,11 +19,14 @@
#pragma once #pragma once
#if DESKTOP_NOTIFICATIONS #include "src/model/notificationdata.h"
#include <libsnore/snore.h> #include <libsnore/snore.h>
#include <QObject> #include <QObject>
#include <memory> #include <memory>
#include <unordered_set>
class DesktopNotify : public QObject class DesktopNotify : public QObject
{ {
@ -31,25 +34,19 @@ class DesktopNotify : public QObject
public: public:
DesktopNotify(); DesktopNotify();
enum class MessageType {
FRIEND,
FRIEND_FILE,
FRIEND_REQUEST,
GROUP,
GROUP_INVITE
};
public slots: public slots:
void notifyMessage(const QString& title, const QString& message); void notifyMessage(const NotificationData& notificationData);
void notifyMessagePixmap(const QString& title, const QString& message, QPixmap avatar);
void notifyMessageSimple(const MessageType type);
private: signals:
void createNotification(const QString& title, const QString& text, Snore::Icon& icon); void notificationClosed();
private slots:
void onNotificationClose(Snore::Notification notification);
private: private:
Snore::SnoreCore& notifyCore; Snore::SnoreCore& notifyCore;
Snore::Application snoreApp; Snore::Application snoreApp;
Snore::Icon snoreIcon; Snore::Icon snoreIcon;
Snore::Notification lastNotification;
uint latestId;
}; };
#endif // DESKTOP_NOTIFICATIONS

View File

@ -294,6 +294,11 @@ void Widget::init()
profileInfo = new ProfileInfo(core, profile); profileInfo = new ProfileInfo(core, profile);
profileForm = new ProfileForm(profileInfo); profileForm = new ProfileForm(profileInfo);
#if DESKTOP_NOTIFICATIONS
notificationGenerator.reset(new NotificationGenerator(settings, profile));
connect(&notifier, &DesktopNotify::notificationClosed, notificationGenerator.get(), &NotificationGenerator::onNotificationActivated);
#endif
// connect logout tray menu action // connect logout tray menu action
connect(actionLogout, &QAction::triggered, profileForm, &ProfileForm::onLogoutClicked); connect(actionLogout, &QAction::triggered, profileForm, &ProfileForm::onLogoutClicked);
@ -1498,7 +1503,7 @@ void Widget::addGroupDialog(Group* group, ContentDialog* dialog)
emit widget->chatroomWidgetClicked(widget); emit widget->chatroomWidgetClicked(widget);
} }
bool Widget::newFriendMessageAlert(const ToxPk& friendId, const QString& text, bool sound, bool file) bool Widget::newFriendMessageAlert(const ToxPk& friendId, const QString& text, bool sound, QString filename, size_t filesize)
{ {
bool hasActive; bool hasActive;
QWidget* currentWindow; QWidget* currentWindow;
@ -1535,17 +1540,9 @@ bool Widget::newFriendMessageAlert(const ToxPk& friendId, const QString& text, b
widget->updateStatusLight(); widget->updateStatusLight();
ui->friendList->trackWidget(widget); ui->friendList->trackWidget(widget);
#if DESKTOP_NOTIFICATIONS #if DESKTOP_NOTIFICATIONS
if (settings.getNotifyHide()) { auto notificationData = filename.isEmpty() ? notificationGenerator->friendMessageNotification(f, text)
notifier.notifyMessageSimple(file ? DesktopNotify::MessageType::FRIEND_FILE : notificationGenerator->fileTransferNotification(f, filename, filesize);
: DesktopNotify::MessageType::FRIEND); notifier.notifyMessage(notificationData);
} else {
QString title = f->getDisplayedName();
if (file) {
title += " - " + tr("File sent");
}
notifier.notifyMessagePixmap(title, text,
Nexus::getProfile()->loadAvatar(f->getPublicKey()));
}
#endif #endif
if (contentDialog == nullptr) { if (contentDialog == nullptr) {
@ -1586,18 +1583,8 @@ bool Widget::newGroupMessageAlert(const GroupId& groupId, const ToxPk& authorPk,
g->setEventFlag(true); g->setEventFlag(true);
widget->updateStatusLight(); widget->updateStatusLight();
#if DESKTOP_NOTIFICATIONS #if DESKTOP_NOTIFICATIONS
if (settings.getNotifyHide()) { auto notificationData = notificationGenerator->groupMessageNotification(g, authorPk, message);
notifier.notifyMessageSimple(DesktopNotify::MessageType::GROUP); notifier.notifyMessage(notificationData);
} else {
Friend* f = FriendList::findFriend(authorPk);
QString title = g->getPeerList().value(authorPk) + " (" + g->getDisplayedName() + ")";
if (!f) {
notifier.notifyMessage(title, message);
} else {
notifier.notifyMessagePixmap(title, message,
Nexus::getProfile()->loadAvatar(f->getPublicKey()));
}
}
#endif #endif
if (contentDialog == nullptr) { if (contentDialog == nullptr) {
@ -1673,11 +1660,8 @@ void Widget::onFriendRequestReceived(const ToxPk& friendPk, const QString& messa
friendRequestsUpdate(); friendRequestsUpdate();
newMessageAlert(window(), isActiveWindow(), true, true); newMessageAlert(window(), isActiveWindow(), true, true);
#if DESKTOP_NOTIFICATIONS #if DESKTOP_NOTIFICATIONS
if (settings.getNotifyHide()) { auto notificationData = notificationGenerator->friendRequestNotification(friendPk, message);
notifier.notifyMessageSimple(DesktopNotify::MessageType::FRIEND_REQUEST); notifier.notifyMessage(notificationData);
} else {
notifier.notifyMessage(friendPk.toString() + tr(" sent you a friend request."), message);
}
#endif #endif
} }
} }
@ -1686,9 +1670,8 @@ void Widget::onFileReceiveRequested(const ToxFile& file)
{ {
const ToxPk& friendPk = FriendList::id2Key(file.friendId); const ToxPk& friendPk = FriendList::id2Key(file.friendId);
newFriendMessageAlert(friendPk, newFriendMessageAlert(friendPk,
file.fileName + " (" {},
+ FileTransferWidget::getHumanReadableSize(file.filesize) + ")", true, file.fileName, file.filesize);
true, true);
} }
void Widget::updateFriendActivity(const Friend& frnd) void Widget::updateFriendActivity(const Friend& frnd)
@ -1934,12 +1917,8 @@ void Widget::onGroupInviteReceived(const GroupInvite& inviteInfo)
groupInvitesUpdate(); groupInvitesUpdate();
newMessageAlert(window(), isActiveWindow(), true, true); newMessageAlert(window(), isActiveWindow(), true, true);
#if DESKTOP_NOTIFICATIONS #if DESKTOP_NOTIFICATIONS
if (settings.getNotifyHide()) { auto notificationData = notificationGenerator->groupInvitationNotification(f);
notifier.notifyMessageSimple(DesktopNotify::MessageType::GROUP_INVITE); notifier.notifyMessage(notificationData);
} else {
notifier.notifyMessagePixmap(f->getDisplayedName() + tr(" invites you to join a group."),
{}, Nexus::getProfile()->loadAvatar(f->getPublicKey()));
}
#endif #endif
} }
} else { } else {

View File

@ -38,6 +38,7 @@
#include "src/model/friendmessagedispatcher.h" #include "src/model/friendmessagedispatcher.h"
#include "src/model/groupmessagedispatcher.h" #include "src/model/groupmessagedispatcher.h"
#if DESKTOP_NOTIFICATIONS #if DESKTOP_NOTIFICATIONS
#include "src/model/notificationgenerator.h"
#include "src/platform/desktop_notifications/desktopnotify.h" #include "src/platform/desktop_notifications/desktopnotify.h"
#endif #endif
@ -127,7 +128,7 @@ public:
void addFriendDialog(const Friend* frnd, ContentDialog* dialog); void addFriendDialog(const Friend* frnd, ContentDialog* dialog);
void addGroupDialog(Group* group, ContentDialog* dialog); void addGroupDialog(Group* group, ContentDialog* dialog);
bool newFriendMessageAlert(const ToxPk& friendId, const QString& text, bool sound = true, bool newFriendMessageAlert(const ToxPk& friendId, const QString& text, bool sound = true,
bool file = false); QString filename = QString(), size_t filesize = 0);
bool newGroupMessageAlert(const GroupId& groupId, const ToxPk& authorPk, const QString& message, bool newGroupMessageAlert(const GroupId& groupId, const ToxPk& authorPk, const QString& message,
bool notify); bool notify);
bool getIsWindowMinimized(); bool getIsWindowMinimized();
@ -360,6 +361,7 @@ private:
MessageProcessor::SharedParams sharedMessageProcessorParams; MessageProcessor::SharedParams sharedMessageProcessorParams;
#if DESKTOP_NOTIFICATIONS #if DESKTOP_NOTIFICATIONS
std::unique_ptr<NotificationGenerator> notificationGenerator;
DesktopNotify notifier; DesktopNotify notifier;
#endif #endif

View File

@ -0,0 +1,45 @@
/*
Copyright © 2020 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "src/core/icoreidhandler.h"
#include <tox/tox.h>
class MockCoreIdHandler : public ICoreIdHandler
{
public:
ToxId getSelfId() const override
{
std::terminate();
return ToxId();
}
ToxPk getSelfPublicKey() const override
{
static uint8_t id[TOX_PUBLIC_KEY_SIZE] = {0};
return ToxPk(id);
}
QString getUsername() const override
{
return "me";
}
};

View File

@ -0,0 +1,82 @@
/*
Copyright © 2020 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "src/core/icoregroupquery.h"
#include <tox/tox.h>
/**
* Mock 1 peer at group number 0
*/
class MockGroupQuery : public ICoreGroupQuery
{
public:
GroupId getGroupPersistentId(uint32_t groupNumber) const override
{
return GroupId(0);
}
uint32_t getGroupNumberPeers(int groupId) const override
{
if (emptyGroup) {
return 1;
}
return 2;
}
QString getGroupPeerName(int groupId, int peerId) const override
{
return QString("peer") + peerId;
}
ToxPk getGroupPeerPk(int groupId, int peerId) const override
{
uint8_t id[TOX_PUBLIC_KEY_SIZE] = {static_cast<uint8_t>(peerId)};
return ToxPk(id);
}
QStringList getGroupPeerNames(int groupId) const override
{
if (emptyGroup) {
return QStringList({QString("me")});
}
return QStringList({QString("me"), QString("other")});
}
bool getGroupAvEnabled(int groupId) const override
{
return false;
}
void setAsEmptyGroup()
{
emptyGroup = true;
}
void setAsFunctionalGroup()
{
emptyGroup = false;
}
private:
bool emptyGroup = false;
};

View File

@ -23,6 +23,9 @@
#include "src/model/message.h" #include "src/model/message.h"
#include "src/persistence/settings.h" #include "src/persistence/settings.h"
#include "test/mock/mockcoreidhandler.h"
#include "test/mock/mockgroupquery.h"
#include <QObject> #include <QObject>
#include <QtTest/QtTest> #include <QtTest/QtTest>
@ -47,85 +50,6 @@ public:
size_t numSentMessages = 0; size_t numSentMessages = 0;
}; };
/**
* Mock 1 peer at group number 0
*/
class MockGroupQuery : public ICoreGroupQuery
{
public:
GroupId getGroupPersistentId(uint32_t groupNumber) const override
{
return GroupId();
}
uint32_t getGroupNumberPeers(int groupId) const override
{
if (emptyGroup) {
return 1;
}
return 2;
}
QString getGroupPeerName(int groupId, int peerId) const override
{
return QString("peer") + peerId;
}
ToxPk getGroupPeerPk(int groupId, int peerId) const override
{
uint8_t id[TOX_PUBLIC_KEY_SIZE] = {static_cast<uint8_t>(peerId)};
return ToxPk(id);
}
QStringList getGroupPeerNames(int groupId) const override
{
if (emptyGroup) {
return QStringList({QString("me")});
}
return QStringList({QString("me"), QString("other")});
}
bool getGroupAvEnabled(int groupId) const override
{
return false;
}
void setAsEmptyGroup()
{
emptyGroup = true;
}
void setAsFunctionalGroup()
{
emptyGroup = false;
}
private:
bool emptyGroup = false;
};
class MockCoreIdHandler : public ICoreIdHandler
{
public:
ToxId getSelfId() const override
{
std::terminate();
return ToxId();
}
ToxPk getSelfPublicKey() const override
{
static uint8_t id[TOX_PUBLIC_KEY_SIZE] = {0};
return ToxPk(id);
}
QString getUsername() const override
{
return "me";
}
};
class MockGroupSettings : public IGroupSettings class MockGroupSettings : public IGroupSettings
{ {
public: public:
@ -139,13 +63,6 @@ public:
blacklist = blist; blacklist = blist;
} }
bool getGroupAlwaysNotify() const override
{
return false;
}
void setGroupAlwaysNotify(bool newValue) override {}
private: private:
QStringList blacklist; QStringList blacklist;
}; };

View File

@ -0,0 +1,371 @@
/*
Copyright © 2019 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre 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.
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "src/model/notificationgenerator.h"
#include "test/mock/mockcoreidhandler.h"
#include "test/mock/mockgroupquery.h"
#include <QObject>
#include <QtTest/QtTest>
namespace
{
class MockNotificationSettings : public INotificationSettings
{
virtual bool getNotify() const override { return true; }
virtual void setNotify(bool newValue) override {}
virtual bool getShowWindow() const override { return true; }
virtual void setShowWindow(bool newValue) override {}
virtual bool getDesktopNotify() const override { return true; }
virtual void setDesktopNotify(bool enabled) override {}
virtual bool getNotifySound() const override { return true; }
virtual void setNotifySound(bool newValue) override {}
virtual bool getNotifyHide() const override { return notifyHide; }
virtual void setNotifyHide(bool newValue) override { notifyHide = newValue; };
virtual bool getBusySound() const override { return true; }
virtual void setBusySound(bool newValue) override {}
virtual bool getGroupAlwaysNotify() const override { return true; }
virtual void setGroupAlwaysNotify(bool newValue) override {}
private:
bool notifyHide = false;
};
} // namespace
class TestNotificationGenerator : public QObject
{
Q_OBJECT
private slots:
void init();
void testSingleFriendMessage();
void testMultipleFriendMessages();
void testNotificationClear();
void testGroupMessage();
void testMultipleGroupMessages();
void testMultipleFriendSourceMessages();
void testMultipleGroupSourceMessages();
void testMixedSourceMessages();
void testFileTransfer();
void testFileTransferAfterMessage();
void testGroupInvitation();
void testGroupInviteUncounted();
void testFriendRequest();
void testFriendRequestUncounted();
void testSimpleFriendMessage();
void testSimpleFileTransfer();
void testSimpleGroupMessage();
void testSimpleFriendRequest();
void testSimpleGroupInvite();
void testSimpleMessageToggle();
private:
std::unique_ptr<INotificationSettings> notificationSettings;
std::unique_ptr<NotificationGenerator> notificationGenerator;
std::unique_ptr<MockGroupQuery> groupQuery;
std::unique_ptr<MockCoreIdHandler> coreIdHandler;
};
void TestNotificationGenerator::init()
{
notificationSettings.reset(new MockNotificationSettings());
notificationGenerator.reset(new NotificationGenerator(*notificationSettings, nullptr));
groupQuery.reset(new MockGroupQuery());
coreIdHandler.reset(new MockCoreIdHandler());
}
void TestNotificationGenerator::testSingleFriendMessage()
{
Friend f(0, ToxPk());
f.setName("friendName");
auto notificationData = notificationGenerator->friendMessageNotification(&f, "test");
QVERIFY(notificationData.title == "friendName");
QVERIFY(notificationData.message == "test");
}
void TestNotificationGenerator::testMultipleFriendMessages()
{
Friend f(0, ToxPk());
f.setName("friendName");
notificationGenerator->friendMessageNotification(&f, "test");
auto notificationData = notificationGenerator->friendMessageNotification(&f, "test2");
QVERIFY(notificationData.title == "2 message(s) from friendName");
QVERIFY(notificationData.message == "test2");
notificationData = notificationGenerator->friendMessageNotification(&f, "test3");
QVERIFY(notificationData.title == "3 message(s) from friendName");
QVERIFY(notificationData.message == "test3");
}
void TestNotificationGenerator::testNotificationClear()
{
Friend f(0, ToxPk());
f.setName("friendName");
notificationGenerator->friendMessageNotification(&f, "test");
// On notification clear we shouldn't see a notification count from the friend
notificationGenerator->onNotificationActivated();
auto notificationData = notificationGenerator->friendMessageNotification(&f, "test2");
QVERIFY(notificationData.title == "friendName");
QVERIFY(notificationData.message == "test2");
}
void TestNotificationGenerator::testGroupMessage()
{
Group g(0, GroupId(0), "groupName", false, "selfName", *groupQuery, *coreIdHandler);
auto sender = groupQuery->getGroupPeerPk(0, 0);
g.updateUsername(sender, "sender1");
auto notificationData = notificationGenerator->groupMessageNotification(&g, sender, "test");
QVERIFY(notificationData.title == "groupName");
QVERIFY(notificationData.message == "sender1: test");
}
void TestNotificationGenerator::testMultipleGroupMessages()
{
Group g(0, GroupId(0), "groupName", false, "selfName", *groupQuery, *coreIdHandler);
auto sender = groupQuery->getGroupPeerPk(0, 0);
g.updateUsername(sender, "sender1");
auto sender2 = groupQuery->getGroupPeerPk(0, 1);
g.updateUsername(sender2, "sender2");
notificationGenerator->groupMessageNotification(&g, sender, "test1");
auto notificationData = notificationGenerator->groupMessageNotification(&g, sender2, "test2");
QVERIFY(notificationData.title == "2 message(s) from groupName");
QVERIFY(notificationData.message == "sender2: test2");
}
void TestNotificationGenerator::testMultipleFriendSourceMessages()
{
Friend f(0, ToxPk());
f.setName("friend1");
Friend f2(1, ToxPk());
f2.setName("friend2");
notificationGenerator->friendMessageNotification(&f, "test1");
auto notificationData = notificationGenerator->friendMessageNotification(&f2, "test2");
QVERIFY(notificationData.title == "2 message(s) from 2 chats");
QVERIFY(notificationData.message == "friend1, friend2");
}
void TestNotificationGenerator::testMultipleGroupSourceMessages()
{
Group g(0, GroupId(QByteArray(32, 0)), "groupName", false, "selfName", *groupQuery, *coreIdHandler);
Group g2(1, GroupId(QByteArray(32, 1)), "groupName2", false, "selfName", *groupQuery, *coreIdHandler);
auto sender = groupQuery->getGroupPeerPk(0, 0);
g.updateUsername(sender, "sender1");
notificationGenerator->groupMessageNotification(&g, sender, "test1");
auto notificationData = notificationGenerator->groupMessageNotification(&g2, sender, "test1");
QVERIFY(notificationData.title == "2 message(s) from 2 chats");
QVERIFY(notificationData.message == "groupName, groupName2");
}
void TestNotificationGenerator::testMixedSourceMessages()
{
Friend f(0, ToxPk());
f.setName("friend");
Group g(0, GroupId(QByteArray(32, 0)), "group", false, "selfName", *groupQuery, *coreIdHandler);
auto sender = groupQuery->getGroupPeerPk(0, 0);
g.updateUsername(sender, "sender1");
notificationGenerator->friendMessageNotification(&f, "test1");
auto notificationData = notificationGenerator->groupMessageNotification(&g, sender, "test2");
QVERIFY(notificationData.title == "2 message(s) from 2 chats");
QVERIFY(notificationData.message == "friend, group");
notificationData = notificationGenerator->fileTransferNotification(&f, "file", 0);
QVERIFY(notificationData.title == "3 message(s) from 2 chats");
QVERIFY(notificationData.message == "friend, group");
}
void TestNotificationGenerator::testFileTransfer()
{
Friend f(0, ToxPk());
f.setName("friend");
auto notificationData = notificationGenerator->fileTransferNotification(&f, "file", 5 * 1024 * 1024 /* 5MB */);
QVERIFY(notificationData.title == "friend - file transfer");
QVERIFY(notificationData.message == "file (5.00MiB)");
}
void TestNotificationGenerator::testFileTransferAfterMessage()
{
Friend f(0, ToxPk());
f.setName("friend");
notificationGenerator->friendMessageNotification(&f, "test1");
auto notificationData = notificationGenerator->fileTransferNotification(&f, "file", 5 * 1024 * 1024 /* 5MB */);
QVERIFY(notificationData.title == "2 message(s) from friend");
QVERIFY(notificationData.message == "Incoming file transfer");
}
void TestNotificationGenerator::testGroupInvitation()
{
Friend f(0, ToxPk());
f.setName("friend");
auto notificationData = notificationGenerator->groupInvitationNotification(&f);
QVERIFY(notificationData.title == "friend invites you to join a group.");
QVERIFY(notificationData.message == "");
}
void TestNotificationGenerator::testGroupInviteUncounted()
{
Friend f(0, ToxPk());
f.setName("friend");
notificationGenerator->friendMessageNotification(&f, "test");
notificationGenerator->groupInvitationNotification(&f);
auto notificationData = notificationGenerator->friendMessageNotification(&f, "test2");
QVERIFY(notificationData.title == "2 message(s) from friend");
QVERIFY(notificationData.message == "test2");
}
void TestNotificationGenerator::testFriendRequest()
{
ToxPk sender(QByteArray(32, 0));
auto notificationData = notificationGenerator->friendRequestNotification(sender, "request");
QVERIFY(notificationData.title == "Friend request received from 0000000000000000000000000000000000000000000000000000000000000000");
QVERIFY(notificationData.message == "request");
}
void TestNotificationGenerator::testFriendRequestUncounted()
{
Friend f(0, ToxPk());
f.setName("friend");
ToxPk sender(QByteArray(32, 0));
notificationGenerator->friendMessageNotification(&f, "test");
notificationGenerator->friendRequestNotification(sender, "request");
auto notificationData = notificationGenerator->friendMessageNotification(&f, "test2");
QVERIFY(notificationData.title == "2 message(s) from friend");
QVERIFY(notificationData.message == "test2");
}
void TestNotificationGenerator::testSimpleFriendMessage()
{
Friend f(0, ToxPk());
f.setName("friend");
notificationSettings->setNotifyHide(true);
auto notificationData = notificationGenerator->friendMessageNotification(&f, "test");
QVERIFY(notificationData.title == "New message");
QVERIFY(notificationData.message == "");
}
void TestNotificationGenerator::testSimpleFileTransfer()
{
Friend f(0, ToxPk());
f.setName("friend");
notificationSettings->setNotifyHide(true);
auto notificationData = notificationGenerator->fileTransferNotification(&f, "file", 0);
QVERIFY(notificationData.title == "Incoming file transfer");
QVERIFY(notificationData.message == "");
}
void TestNotificationGenerator::testSimpleGroupMessage()
{
Group g(0, GroupId(0), "groupName", false, "selfName", *groupQuery, *coreIdHandler);
auto sender = groupQuery->getGroupPeerPk(0, 0);
g.updateUsername(sender, "sender1");
notificationSettings->setNotifyHide(true);
auto notificationData = notificationGenerator->groupMessageNotification(&g, sender, "test");
QVERIFY(notificationData.title == "New group message");
QVERIFY(notificationData.message == "");
}
void TestNotificationGenerator::testSimpleFriendRequest()
{
ToxPk sender(QByteArray(32, 0));
notificationSettings->setNotifyHide(true);
auto notificationData = notificationGenerator->friendRequestNotification(sender, "request");
QVERIFY(notificationData.title == "Friend request received");
QVERIFY(notificationData.message == "");
}
void TestNotificationGenerator::testSimpleGroupInvite()
{
Friend f(0, ToxPk());
f.setName("friend");
notificationSettings->setNotifyHide(true);
auto notificationData = notificationGenerator->groupInvitationNotification(&f);
QVERIFY(notificationData.title == "Group invite received");
QVERIFY(notificationData.message == "");
}
void TestNotificationGenerator::testSimpleMessageToggle()
{
Friend f(0, ToxPk());
f.setName("friend");
notificationSettings->setNotifyHide(true);
notificationGenerator->friendMessageNotification(&f, "test");
notificationSettings->setNotifyHide(false);
auto notificationData = notificationGenerator->friendMessageNotification(&f, "test2");
QVERIFY(notificationData.title == "2 message(s) from friend");
QVERIFY(notificationData.message == "test2");
}
QTEST_GUILESS_MAIN(TestNotificationGenerator)
#include "notificationgenerator_test.moc"