Skip to content

Commit

Permalink
Add an incoming talk call notification to the desktop client
Browse files Browse the repository at this point in the history
Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>

Co-authored-by: Camila <hello@camila.codes>
  • Loading branch information
2 people authored and mgallien committed Apr 27, 2022
1 parent 8ad08cd commit 666bc8d
Show file tree
Hide file tree
Showing 21 changed files with 552 additions and 31 deletions.
1 change: 1 addition & 0 deletions resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
<file>src/gui/tray/ActivityItemActions.qml</file>
<file>src/gui/tray/ActivityItemContent.qml</file>
<file>src/gui/tray/TalkReplyTextField.qml</file>
<file>src/gui/tray/CallNotificationDialog.qml</file>
</qresource>
</RCC>
13 changes: 13 additions & 0 deletions src/gui/generalsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ GeneralSettings::GeneralSettings(QWidget *parent)
this, &GeneralSettings::slotToggleOptionalServerNotifications);
_ui->serverNotificationsCheckBox->setToolTip(tr("Server notifications that require attention."));

connect(_ui->callNotificationsCheckBox, &QAbstractButton::toggled,
this, &GeneralSettings::slotToggleCallNotifications);
_ui->callNotificationsCheckBox->setToolTip(tr("Show call notification dialogs."));

connect(_ui->showInExplorerNavigationPaneCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotShowInExplorerNavigationPane);

// Rename 'Explorer' appropriately on non-Windows
Expand Down Expand Up @@ -247,6 +251,8 @@ void GeneralSettings::loadMiscSettings()
ConfigFile cfgFile;
_ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons());
_ui->serverNotificationsCheckBox->setChecked(cfgFile.optionalServerNotifications());
_ui->callNotificationsCheckBox->setEnabled(_ui->serverNotificationsCheckBox->isEnabled());
_ui->callNotificationsCheckBox->setChecked(cfgFile.showCallNotifications());
_ui->showInExplorerNavigationPaneCheckBox->setChecked(cfgFile.showInExplorerNavigationPane());
_ui->crashreporterCheckBox->setChecked(cfgFile.crashReporter());
auto newFolderLimit = cfgFile.newBigFolderSizeLimit();
Expand Down Expand Up @@ -428,6 +434,13 @@ void GeneralSettings::slotToggleOptionalServerNotifications(bool enable)
{
ConfigFile cfgFile;
cfgFile.setOptionalServerNotifications(enable);
_ui->callNotificationsCheckBox->setEnabled(enable);
}

void GeneralSettings::slotToggleCallNotifications(bool enable)
{
ConfigFile cfgFile;
cfgFile.setShowCallNotifications(enable);
}

void GeneralSettings::slotShowInExplorerNavigationPane(bool checked)
Expand Down
1 change: 1 addition & 0 deletions src/gui/generalsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private slots:
void saveMiscSettings();
void slotToggleLaunchOnStartup(bool);
void slotToggleOptionalServerNotifications(bool);
void slotToggleCallNotifications(bool);
void slotShowInExplorerNavigationPane(bool);
void slotIgnoreFilesEditor();
void slotCreateDebugArchive();
Expand Down
11 changes: 9 additions & 2 deletions src/gui/generalsettings.ui
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>554</width>
<height>558</height>
<width>556</width>
<height>563</height>
</rect>
</property>
<property name="windowTitle">
Expand Down Expand Up @@ -90,6 +90,13 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="callNotificationsCheckBox">
<property name="text">
<string>Show Call Notifications</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down
214 changes: 192 additions & 22 deletions src/gui/systray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QVariantMap>
#include <QScreen>
#include <QMenu>
#include <QGuiApplication>
Expand Down Expand Up @@ -159,6 +160,44 @@ void Systray::create()
}
}

void Systray::createCallDialog(const Activity &callNotification)
{
qCDebug(lcSystray) << "Starting a new call dialog for notification with id: " << callNotification._id << "with text: " << callNotification._subject;

if (_trayEngine && !_callsAlreadyNotified.contains(callNotification._id)) {
const QVariantMap talkNotificationData{
{"conversationToken", callNotification._talkNotificationData.conversationToken},
{"messageId", callNotification._talkNotificationData.messageId},
{"messageSent", callNotification._talkNotificationData.messageSent},
{"userAvatar", callNotification._talkNotificationData.userAvatar},
};

QVariantList links;
for(const auto &link : callNotification._links) {
links.append(QVariantMap{
{"imageSource", link._imageSource},
{"imageSourceHovered", link._imageSourceHovered},
{"label", link._label},
{"link", link._link},
{"primary", link._primary},
{"verb", link._verb},
});
}

const QVariantMap initialProperties{
{"talkNotificationData", talkNotificationData},
{"links", links},
{"subject", callNotification._subject},
{"link", callNotification._link},
};

const auto callDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/CallNotificationDialog.qml"));
callDialog->createWithInitialProperties(initialProperties);

_callsAlreadyNotified.insert(callNotification._id);
}
}

void Systray::slotNewUserSelected()
{
if (_trayEngine) {
Expand Down Expand Up @@ -308,6 +347,30 @@ void Systray::forceWindowInit(QQuickWindow *window) const
#endif
}

void Systray::positionNotificationWindow(QQuickWindow *window) const
{
if (!useNormalWindow()) {
window->setScreen(currentScreen());
if(geometry().isValid()) {
// On OSes where the QSystemTrayIcon geometry method isn't borked, we can actually figure out where the system tray is located
// We can therefore use our normal routines
const auto position = computeNotificationPosition(window->width(), window->height());
window->setPosition(position);
} else if (QProcessEnvironment::systemEnvironment().contains(QStringLiteral("XDG_CURRENT_DESKTOP")) &&
(QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP")).contains(QStringLiteral("GNOME")))) {
// We can safely hardcode the top-right position for the notification when running GNOME
const auto position = computeNotificationPosition(window->width(), window->height(), 0, NotificationPosition::TopRight);
window->setPosition(position);
} else {
// For other DEs we play it safe and place the notification in the centre of the screen
const QPoint windowAdjustment(window->geometry().width() / 2, window->geometry().height() / 2);
const auto position = currentScreen()->geometry().center();// - windowAdjustment;
window->setPosition(position);
}
// TODO: Get actual notification positions for the DEs
}
}

QScreen *Systray::currentScreen() const
{
const auto screen = QGuiApplication::screenAt(QCursor::pos());
Expand Down Expand Up @@ -446,8 +509,85 @@ QPoint Systray::computeWindowReferencePoint() const
Q_UNREACHABLE();
}

QPoint Systray::computeNotificationReferencePoint(int spacing, NotificationPosition position) const
{
auto trayIconCenter = calcTrayIconCenter();
auto taskbarScreenEdge = taskbarOrientation();
auto taskbarRect = taskbarGeometry();
const auto screenRect = currentScreenRect();

if(position == NotificationPosition::TopLeft) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(0, 0);
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::TopRight) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(screenRect.width(), 0);
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::BottomLeft) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(0, screenRect.height());
taskbarRect = QRect(0, 0, screenRect.width(), 32);
} else if(position == NotificationPosition::BottomRight) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(screenRect.width(), screenRect.height());
taskbarRect = QRect(0, 0, screenRect.width(), 32);
}

qCDebug(lcSystray) << "screenRect:" << screenRect;
qCDebug(lcSystray) << "taskbarRect:" << taskbarRect;
qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "trayIconCenter:" << trayIconCenter;

switch(taskbarScreenEdge) {
case TaskBarPosition::Bottom:
return {
trayIconCenter.x() < screenRect.center().x() ? screenRect.left() + spacing : screenRect.right() - spacing,
screenRect.bottom() - taskbarRect.height() - spacing
};
case TaskBarPosition::Left:
return {
screenRect.left() + taskbarRect.width() + spacing,
trayIconCenter.y() < screenRect.center().y() ? screenRect.top() + spacing : screenRect.bottom() - spacing
};
case TaskBarPosition::Top:
return {
trayIconCenter.x() < screenRect.center().x() ? screenRect.left() + spacing : screenRect.right() - spacing,
screenRect.top() + taskbarRect.height() + spacing
};
case TaskBarPosition::Right:
return {
screenRect.right() - taskbarRect.width() - spacing,
trayIconCenter.y() < screenRect.center().y() ? screenRect.top() + spacing : screenRect.bottom() - spacing
};
}
Q_UNREACHABLE();
}

QRect Systray::computeWindowRect(int spacing, const QPoint &topLeft, const QPoint &bottomRight) const
{
const auto screenRect = currentScreenRect();
const auto rect = QRect(topLeft, bottomRight);
auto offset = QPoint();

if (rect.left() < screenRect.left()) {
offset.setX(screenRect.left() - rect.left() + spacing);
} else if (rect.right() > screenRect.right()) {
offset.setX(screenRect.right() - rect.right() - spacing);
}

if (rect.top() < screenRect.top()) {
offset.setY(screenRect.top() - rect.top() + spacing);
} else if (rect.bottom() > screenRect.bottom()) {
offset.setY(screenRect.bottom() - rect.bottom() - spacing);
}

return rect.translated(offset);
}

QPoint Systray::computeWindowPosition(int width, int height) const
{
constexpr auto spacing = 4;
const auto referencePoint = computeWindowReferencePoint();

const auto taskbarScreenEdge = taskbarOrientation();
Expand All @@ -467,44 +607,74 @@ QPoint Systray::computeWindowPosition(int width, int height) const
Q_UNREACHABLE();
}();
const auto bottomRight = topLeft + QPoint(width, height);
const auto windowRect = [=]() {
const auto rect = QRect(topLeft, bottomRight);
auto offset = QPoint();

if (rect.left() < screenRect.left()) {
offset.setX(screenRect.left() - rect.left() + 4);
} else if (rect.right() > screenRect.right()) {
offset.setX(screenRect.right() - rect.right() - 4);
}
const auto windowRect = computeWindowRect(spacing, topLeft, bottomRight);

if (rect.top() < screenRect.top()) {
offset.setY(screenRect.top() - rect.top() + 4);
} else if (rect.bottom() > screenRect.bottom()) {
offset.setY(screenRect.bottom() - rect.bottom() - 4);
}
qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "screenRect:" << screenRect;
qCDebug(lcSystray) << "windowRect (reference)" << QRect(topLeft, bottomRight);
qCDebug(lcSystray) << "windowRect (adjusted)" << windowRect;

return windowRect.topLeft();
}

QPoint Systray::computeNotificationPosition(int width, int height, int spacing, NotificationPosition position) const
{
const auto referencePoint = computeNotificationReferencePoint(spacing, position);

return rect.translated(offset);
auto trayIconCenter = calcTrayIconCenter();
auto taskbarScreenEdge = taskbarOrientation();
const auto screenRect = currentScreenRect();

if(position == NotificationPosition::TopLeft) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(0, 0);
} else if(position == NotificationPosition::TopRight) {
taskbarScreenEdge = TaskBarPosition::Top;
trayIconCenter = QPoint(screenRect.width(), 0);
} else if(position == NotificationPosition::BottomLeft) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(0, screenRect.height());
} else if(position == NotificationPosition::BottomRight) {
taskbarScreenEdge = TaskBarPosition::Bottom;
trayIconCenter = QPoint(screenRect.width(), screenRect.height());
}

const auto topLeft = [=]() {
switch(taskbarScreenEdge) {
case TaskBarPosition::Bottom:
return trayIconCenter.x() < screenRect.center().x() ? referencePoint - QPoint(0, height) : referencePoint - QPoint(width, height);
case TaskBarPosition::Left:
return trayIconCenter.y() < screenRect.center().y() ? referencePoint : referencePoint - QPoint(0, height);
case TaskBarPosition::Top:
return trayIconCenter.x() < screenRect.center().x() ? referencePoint : referencePoint - QPoint(width, 0);
case TaskBarPosition::Right:
return trayIconCenter.y() < screenRect.center().y() ? referencePoint - QPoint(width, 0) : QPoint(width, height);
}
Q_UNREACHABLE();
}();
const auto bottomRight = topLeft + QPoint(width, height);
const auto windowRect = computeWindowRect(spacing, topLeft, bottomRight);

qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
qCDebug(lcSystray) << "screenRect:" << screenRect;
qCDebug(lcSystray) << "windowRect (reference)" << QRect(topLeft, bottomRight);
qCDebug(lcSystray) << "windowRect (adjusted)" << windowRect;
qCDebug(lcSystray) << "referencePoint" << referencePoint;

return windowRect.topLeft();
}

QPoint Systray::calcTrayIconCenter() const
{
// QSystemTrayIcon::geometry() is broken for ages on most Linux DEs (invalid geometry returned)
// thus we can use this only for Windows and macOS
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
auto trayIconCenter = geometry().center();
return trayIconCenter;
#else
if(geometry().isValid()) {
// QSystemTrayIcon::geometry() is broken for ages on most Linux DEs (invalid geometry returned)
// thus we can use this only for Windows and macOS
auto trayIconCenter = geometry().center();
return trayIconCenter;
}

// On Linux, fall back to mouse position (assuming tray icon is activated by mouse click)
return QCursor::pos(currentScreen());
#endif
}

AccessManagerFactory::AccessManagerFactory()
Expand Down
10 changes: 10 additions & 0 deletions src/gui/systray.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class Systray

enum class TaskBarPosition { Bottom, Left, Top, Right };
Q_ENUM(TaskBarPosition);

enum class NotificationPosition { Default, TopLeft, TopRight, BottomLeft, BottomRight };
Q_ENUM(NotificationPosition);

void setTrayEngine(QQmlApplicationEngine *trayEngine);
void create();
Expand All @@ -72,13 +75,15 @@ class Systray
bool isOpen();
QString windowTitle() const;
bool useNormalWindow() const;
void createCallDialog(const Activity &callNotification);

Q_INVOKABLE void pauseResumeSync();
Q_INVOKABLE bool syncIsPaused();
Q_INVOKABLE void setOpened();
Q_INVOKABLE void setClosed();
Q_INVOKABLE void positionWindow(QQuickWindow *window) const;
Q_INVOKABLE void forceWindowInit(QQuickWindow *window) const;
Q_INVOKABLE void positionNotificationWindow(QQuickWindow *window) const;

signals:
void currentUserChanged();
Expand Down Expand Up @@ -110,16 +115,21 @@ private slots:
QScreen *currentScreen() const;
QRect currentScreenRect() const;
QPoint computeWindowReferencePoint() const;
QPoint computeNotificationReferencePoint(int spacing = 20, NotificationPosition position = NotificationPosition::Default) const;
QPoint calcTrayIconCenter() const;
TaskBarPosition taskbarOrientation() const;
QRect taskbarGeometry() const;
QRect computeWindowRect(int spacing, const QPoint &topLeft, const QPoint &bottomRight) const;
QPoint computeWindowPosition(int width, int height) const;
QPoint computeNotificationPosition(int width, int height, int spacing = 20, NotificationPosition position = NotificationPosition::Default) const;

bool _isOpen = false;
bool _syncIsPaused = true;
QPointer<QQmlApplicationEngine> _trayEngine;

AccessManagerFactory _accessManagerFactory;

QSet<qlonglong> _callsAlreadyNotified;
};

} // namespace OCC
Expand Down
1 change: 1 addition & 0 deletions src/gui/tray/ActivityItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ MouseArea {
readonly property bool isChatActivity: model.objectType === "chat" || model.objectType === "room" || model.objectType === "call"
readonly property bool isTalkReplyPossible: model.conversationToken !== ""
property bool isTalkReplyOptionVisible: model.messageSent !== ""
readonly property bool isCallActivity: model.objectType === "call"

signal fileActivityButtonClicked(string absolutePath)

Expand Down
Loading

0 comments on commit 666bc8d

Please sign in to comment.