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
claucambra and Camila committed Apr 23, 2022
1 parent 1e2c158 commit 907c832
Show file tree
Hide file tree
Showing 17 changed files with 472 additions and 29 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>
170 changes: 148 additions & 22 deletions src/gui/systray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QQuickView>
#include <QVariantMap>
#include <QScreen>
#include <QMenu>
#include <QGuiApplication>
Expand Down Expand Up @@ -159,6 +161,44 @@ void Systray::create()
}
}

void Systray::createCallDialog(const Activity &callNotification)
{
qCDebug(lcSystray) << "Starting a new call dialog.";

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.append(callNotification._id);
}
}

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

void Systray::positionNotificationWindow(QQuickWindow *window) const
{
if (!useNormalWindow()) {
window->setScreen(currentScreen());
if(geometry().isValid()) {
const auto position = computeNotificationPosition(window->width(), window->height());
window->setPosition(position);
}
}
}

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

QPoint Systray::computeNotificationReferencePoint() const
{
constexpr auto spacing = 20;
const auto trayIconCenter = calcTrayIconCenter();
const auto taskbarRect = taskbarGeometry();
const auto taskbarScreenEdge = taskbarOrientation();
const auto screenRect = currentScreenRect();

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

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,24 +577,40 @@ 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 rect.translated(offset);
return windowRect.topLeft();
}

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

const auto trayIconCenter = calcTrayIconCenter();
const auto taskbarScreenEdge = taskbarOrientation();
const auto screenRect = currentScreenRect();

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;
Expand All @@ -496,15 +622,15 @@ QPoint Systray::computeWindowPosition(int width, int height) const

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
12 changes: 12 additions & 0 deletions src/gui/systray.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "tray/usermodel.h"

#include <QQmlNetworkAccessManagerFactory>
#include <QTemporaryFile>

class QScreen;
class QQmlApplicationEngine;
Expand Down Expand Up @@ -72,13 +73,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 +113,25 @@ private slots:
QScreen *currentScreen() const;
QRect currentScreenRect() const;
QPoint computeWindowReferencePoint() const;
QPoint computeNotificationReferencePoint() 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) const;

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

AccessManagerFactory _accessManagerFactory;

QVector<qlonglong> _callsAlreadyNotified;

QScopedPointer<QTemporaryFile> _tempCallRingtoneFile;

QRect _storedSelfGeometry;
};

} // 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 907c832

Please sign in to comment.