Add notification module

pull/15/head
reionwong 5 years ago
parent 335f79aaf8
commit bf487ebb2d

@ -32,5 +32,6 @@ add_subdirectory(cpufreq)
add_subdirectory(chotkeys)
add_subdirectory(cupdatecursor)
add_subdirectory(gmenuproxy)
add_subdirectory(notificationd)
install(FILES cutefish DESTINATION /etc/ COMPONENT Runtime)
install(FILES cutefish DESTINATION /etc/ COMPONENT Runtime)

@ -0,0 +1,23 @@
set(SRCS main.cpp
screenhelper.cpp
notificationsmodel.cpp
notificationserver.cpp
notification.cpp
resources.qrc
)
qt_add_dbus_adaptor(SRCS org.freedesktop.Notifications.xml notificationserver.h NotificationServer)
add_executable(cutefish-notificationd ${SRCS})
target_link_libraries(cutefish-notificationd
Qt5::Core
Qt5::DBus
Qt5::Quick
Qt5::Widgets
)
install(TARGETS cutefish-notificationd
DESTINATION /usr/bin
COMPONENT Runtime
)

@ -0,0 +1,10 @@
<svg width="30" height="30" version="1.1" viewBox="0 0 7.9375 7.9375" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0 -289.06)">
<rect x=".86407" y="289.92" width="6.2094" height="6.2267" ry="6.589e-6" opacity="0" stroke-width=".18433" style="paint-order:markers stroke fill"/>
<g transform="matrix(.0065117 0 0 .0065117 2.3018 291.36)" fill="#fff">
<g fill="#fff">
<path d="m284.29 256 221.86-221.86c7.811-7.811 7.811-20.475 0-28.285s-20.475-7.811-28.285 0l-221.86 221.86-221.86-221.86c-7.811-7.811-20.475-7.811-28.285 0s-7.811 20.475 0 28.285l221.86 221.86-221.86 221.86c-7.811 7.811-7.811 20.475 0 28.285 3.905 3.905 9.024 5.857 14.143 5.857s10.237-1.952 14.143-5.857l221.86-221.86 221.86 221.86c3.905 3.905 9.024 5.857 14.143 5.857s10.237-1.952 14.143-5.857c7.811-7.811 7.811-20.475 0-28.285z" fill="#fff"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 866 B

@ -0,0 +1,10 @@
<svg width="30" height="30" version="1.1" viewBox="0 0 7.9375 7.9375" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0 -289.06)">
<rect x=".86407" y="289.92" width="6.2094" height="6.2267" ry="6.589e-6" opacity="0" stroke-width=".18433" style="paint-order:markers stroke fill"/>
<g transform="matrix(.0065117 0 0 .0065117 2.3018 291.36)" fill="#383838">
<g fill="#383838">
<path d="m284.29 256 221.86-221.86c7.811-7.811 7.811-20.475 0-28.285s-20.475-7.811-28.285 0l-221.86 221.86-221.86-221.86c-7.811-7.811-20.475-7.811-28.285 0s-7.811 20.475 0 28.285l221.86 221.86-221.86 221.86c-7.811 7.811-7.811 20.475 0 28.285 3.905 3.905 9.024 5.857 14.143 5.857s10.237-1.952 14.143-5.857l221.86-221.86 221.86 221.86c3.905 3.905 9.024 5.857 14.143 5.857s10.237-1.952 14.143-5.857c7.811-7.811 7.811-20.475 0-28.285z" fill="#383838"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 875 B

@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: Reion Wong <reion@cutefishos.com>
*
* 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
* 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 <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "view.h"
#include "notificationsmodel.h"
#include "screenhelper.h"
int main(int argc, char *argv[])
{
// QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
QApplication a(argc, argv);
qmlRegisterType<NotificationsModel>("Cutefish.Notification", 1, 0, "NotificationsModel");
qmlRegisterType<ScreenHelper>("Cutefish.Notification", 1, 0, "ScreenHelper");
QQmlApplicationEngine engine;
engine.load(QUrl("qrc:/qml/main.qml"));
return a.exec();
}

@ -0,0 +1,20 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: Reion Wong <reion@cutefishos.com>
*
* 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
* 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 "notification.h"

@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: Reion Wong <reion@cutefishos.com>
*
* 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
* 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 NOTIFICATION_H
#define NOTIFICATION_H
#include <QObject>
#include <QDateTime>
class Notification
{
public:
uint id = 0;
QString service;
QString summary;
QString body;
QString appName;
QString appIcon;
QStringList actions;
int timeout = -1;
QDateTime created;
QDateTime updated;
};
#endif // NOTIFICATION_H

@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2021 Reion Wong <reion@cutefishos.com>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "notificationserver.h"
#include "notificationsadaptor.h"
#include <QDebug>
static NotificationServer *NOTIFICATION_SERVER_SELF = nullptr;
NotificationServer *NotificationServer::self()
{
if (!NOTIFICATION_SERVER_SELF)
NOTIFICATION_SERVER_SELF = new NotificationServer;
return NOTIFICATION_SERVER_SELF;
}
NotificationServer::NotificationServer(QObject *parent)
: QObject(parent)
, m_notificationWatcher(nullptr)
{
new NotificationsAdaptor(this);
QDBusConnectionInterface *iface = QDBusConnection::sessionBus().interface();
QDBusConnection::sessionBus().registerObject("/org/freedesktop/Notifications", this);
auto registration = iface->registerService("org.freedesktop.Notifications",
QDBusConnectionInterface::ReplaceExistingService,
QDBusConnectionInterface::DontAllowReplacement);
}
uint NotificationServer::Notify(const QString &app_name,
uint replaces_id,
const QString &app_icon,
const QString &summary,
const QString &body,
const QStringList &actions,
const QVariantMap &hints,
int timeout)
{
Q_UNUSED(hints);
uint id = 0;
const bool wasReplaced = replaces_id > 0;
if (wasReplaced) {
id = replaces_id;
} else {
if (!m_highestId)
++m_highestId;
id = m_highestId;
++m_highestId;
}
Notification notification;
notification.id = id;
notification.created = QDateTime::currentDateTimeUtc();
notification.service = message().service();
notification.summary = summary;
notification.body = body;
notification.appName = app_name;
notification.appIcon = app_icon;
notification.actions = actions;
notification.timeout = timeout;
uint pid = 0;
QDBusReply<uint> pidReply = connection().interface()->servicePid(message().service());
if (pidReply.isValid()) {
pid = pidReply.value();
}
if (pid > 0) {
// 查找 app name
if (notification.appName.isEmpty()) {
}
}
if (wasReplaced) {
notification.updated = QDateTime::currentDateTimeUtc();
emit notificationReplaced(replaces_id, notification);
} else {
emit notificationAdded(notification);
}
return id;
}
void NotificationServer::CloseNotification(uint id)
{
Q_UNUSED(id);
}
QStringList NotificationServer::GetCapabilities() const
{
return QStringList{ QStringLiteral("body"),
QStringLiteral("body-hyperlinks"),
QStringLiteral("body-markup"),
QStringLiteral("body-images"),
QStringLiteral("icon-static"),
QStringLiteral("actions"),
QStringLiteral("persistence"),
QStringLiteral("inline-reply"),
QStringLiteral("inhibitions") };
}
QString NotificationServer::GetServerInformation(QString &vendor, QString &version, QString &specVersion) const
{
vendor = "CutefishOS";
version = "1.0";
specVersion = "1.2";
return "Cutefish";
}
uint NotificationServer::Inhibit(const QString &desktop_entry, const QString &reason, const QVariantMap &hints)
{
return 0;
}
void NotificationServer::UnInhibit(uint cookie)
{
Q_UNUSED(cookie)
}
bool NotificationServer::inhibited() const
{
return false;
}
void NotificationServer::RegisterWatcher()
{
m_notificationWatcher->addWatchedService(message().service());
}
void NotificationServer::UnRegisterWatcher()
{
m_notificationWatcher->removeWatchedService(message().service());
}
void NotificationServer::InvokeAction(uint id, const QString &actionKey)
{
Q_EMIT ActionInvoked(id, actionKey);
}
void NotificationServer::closeNotification(uint id, NotificationServer::CloseReason reason)
{
emit notificationRemoved(id, reason);
}

@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2021 Reion Wong <reion@cutefishos.com>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef NOTIFICATIONSERVER_H
#define NOTIFICATIONSERVER_H
#include <QObject>
#include <QDBusConnection>
#include <QDBusServiceWatcher>
#include <QDBusContext>
#include "notification.h"
class NotificationServer : public QObject, protected QDBusContext
{
Q_OBJECT
public:
enum class CloseReason {
Expired = 1, ///< The notification timed out
DismissedByUser = 2, ///< The user explicitly closed or acknowledged the notification
Revoked = 3, ///< The notification was revoked by the issuing app because it is no longer relevant
};
Q_ENUM(CloseReason)
static NotificationServer *self();
explicit NotificationServer(QObject *parent = nullptr);
// DBus
uint Notify(const QString &app_name,
uint replaces_id,
const QString &app_icon,
const QString &summary,
const QString &body,
const QStringList &actions,
const QVariantMap &hints,
int timeout);
void CloseNotification(uint id);
QStringList GetCapabilities() const;
QString GetServerInformation(QString &vendor, QString &version, QString &specVersion) const;
// Inhibitions
uint Inhibit(const QString &desktop_entry, const QString &reason, const QVariantMap &hints);
void UnInhibit(uint cookie);
bool inhibited() const; // property getter
// Notifition watcher
void RegisterWatcher();
void UnRegisterWatcher();
void InvokeAction(uint id, const QString &actionKey);
// Self
void closeNotification(uint id, CloseReason reason);
Q_SIGNALS:
// DBus
void NotificationClosed(uint id, uint reason);
void ActionInvoked(uint id, const QString &actionKey);
// Self
void notificationAdded(const Notification &notification);
void notificationReplaced(uint replacedId, const Notification &notification);
void notificationRemoved(uint id, CloseReason reason);
private:
uint m_highestId = -1;
Notification m_lastNotification;
QDBusServiceWatcher *m_notificationWatcher;
};
#endif // NOTIFICATIONSERVER_H

@ -0,0 +1,221 @@
/*
* SPDX-FileCopyrightText: 2021 Reion Wong <reion@cutefishos.com>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "notificationsmodel.h"
#include "notification.h"
#include <QMetaEnum>
#include <QDebug>
static const int s_notificationsLimit = 1000;
NotificationsModel::NotificationsModel(QObject *parent)
: QAbstractListModel(parent)
{
m_pendingRemovalTimer.setSingleShot(true);
m_pendingRemovalTimer.setInterval(50);
connect(&m_pendingRemovalTimer, &QTimer::timeout, this, [this] {
QVector<int> rowsToBeRemoved;
rowsToBeRemoved.reserve(m_pendingRemovals.count());
for (uint id : qAsConst(m_pendingRemovals)) {
int row = rowOfNotification(id); // oh the complexity...
if (row == -1) {
continue;
}
rowsToBeRemoved.append(row);
}
removeRows(rowsToBeRemoved);
});
connect(NotificationServer::self(), &NotificationServer::notificationAdded, this, &NotificationsModel::onNotificationAdded);
connect(NotificationServer::self(), &NotificationServer::notificationReplaced, this, &NotificationsModel::onNotificationReplaced);
connect(NotificationServer::self(), &NotificationServer::notificationRemoved, this, &NotificationsModel::onNotificationRemoved);
}
QVariant NotificationsModel::data(const QModelIndex &index, int role) const
{
const Notification &notification = m_notifications.at(index.row());
switch (role) {
case NotificationsModel::IdRole:
return notification.id;
case NotificationsModel::SummaryRole:
return notification.summary;
case NotificationsModel::ImageRole:
return "";
case NotificationsModel::CreatedRole:
return notification.created;
case NotificationsModel::BodyRole:
return notification.body;
case NotificationsModel::IconNameRole:
return notification.appIcon;
default:
break;
}
return QVariant();
}
bool NotificationsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
// Notification &notification = m_notifications[index.row()];
bool dirty = false;
return dirty;
}
int NotificationsModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_notifications.size();
}
QHash<int, QByteArray> NotificationsModel::roleNames() const
{
static QHash<int, QByteArray> s_roles;
if (s_roles.isEmpty()) {
// This generates role names from the Roles enum in the form of: FooRole -> foo
const QMetaEnum e = QMetaEnum::fromType<NotificationsModel::Roles>();
// Qt built-in roles we use
s_roles.insert(Qt::DisplayRole, QByteArrayLiteral("display"));
s_roles.insert(Qt::DecorationRole, QByteArrayLiteral("decoration"));
for (int i = 0; i < e.keyCount(); ++i) {
const int value = e.value(i);
QByteArray key(e.key(i));
key[0] = key[0] + 32; // lower case first letter
key.chop(4); // strip "Role" suffix
s_roles.insert(value, key);
}
s_roles.insert(NotificationsModel::IdRole, QByteArrayLiteral("notificationId")); // id is QML-reserved
}
return s_roles;
}
void NotificationsModel::close(uint id)
{
if (rowOfNotification(id) > -1) {
NotificationServer::self()->closeNotification(id, NotificationServer::CloseReason::DismissedByUser);
}
}
int NotificationsModel::rowOfNotification(uint id) const
{
auto it = std::find_if(m_notifications.constBegin(), m_notifications.constEnd(), [id](const Notification &item) {
return item.id == id;
});
if (it == m_notifications.constEnd()) {
return -1;
}
return std::distance(m_notifications.constBegin(), it);
}
void NotificationsModel::removeRows(const QVector<int> &rows)
{
if (rows.isEmpty()) {
return;
}
QVector<int> rowsToBeRemoved(rows);
std::sort(rowsToBeRemoved.begin(), rowsToBeRemoved.end());
QVector<QPair<int, int>> clearQueue;
QPair<int, int> clearRange{rowsToBeRemoved.first(), rowsToBeRemoved.first()};
for (int row : rowsToBeRemoved) {
if (row > clearRange.second + 1) {
clearQueue.append(clearRange);
clearRange.first = row;
}
clearRange.second = row;
}
if (clearQueue.isEmpty() || clearQueue.last() != clearRange) {
clearQueue.append(clearRange);
}
int rowsRemoved = 0;
for (int i = clearQueue.count() - 1; i >= 0; --i) {
const auto &range = clearQueue.at(i);
beginRemoveRows(QModelIndex(), range.first, range.second);
for (int j = range.second; j >= range.first; --j) {
m_notifications.removeAt(j);
++rowsRemoved;
}
endRemoveRows();
}
Q_ASSERT(rowsRemoved == rowsToBeRemoved.count());
m_pendingRemovals.clear();
}
void NotificationsModel::onNotificationAdded(const Notification &notification)
{
if (m_notifications.size() >= s_notificationsLimit) {
const int cleanupCount = s_notificationsLimit / 2;
beginRemoveRows(QModelIndex(), 0, cleanupCount - 1);
for (int i = 0; i < cleanupCount; ++i) {
m_notifications.removeAt(0);
}
endRemoveRows();
}
beginInsertRows(QModelIndex(), m_notifications.size(), m_notifications.size());
m_notifications.append(std::move(notification));
endInsertRows();
}
void NotificationsModel::onNotificationReplaced(uint replacedId, const Notification &notification)
{
}
void NotificationsModel::onNotificationRemoved(uint removedId, NotificationServer::CloseReason reason)
{
const int row = rowOfNotification(removedId);
if (row == -1) {
return;
}
// When a notification expired, keep it around in the history and mark it as such
if (reason == NotificationServer::CloseReason::Expired) {
const QModelIndex idx = NotificationsModel::index(row, 0);
Notification &notification = m_notifications[row];
// notification.setExpired(true);
notification.actions.clear();
emit dataChanged(idx, idx);
return;
}
// Otherwise if explicitly closed by either user or app, mark it for removal
// some apps are notorious for closing a bunch of notifications at once
// causing newer notifications to move up and have a dialogs created for them
// just to then be discarded causing excess CPU usage
if (!m_pendingRemovals.contains(removedId)) {
m_pendingRemovals.append(removedId);
}
if (!m_pendingRemovalTimer.isActive()) {
m_pendingRemovalTimer.start();
}
}

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2021 Reion Wong <reion@cutefishos.com>
* SPDX-FileCopyrightText: 2018-2019 Kai Uwe Broulik <kde@privat.broulik.de>
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef NOTIFICATIONSMODEL_H
#define NOTIFICATIONSMODEL_H
#include <QAbstractListModel>
#include <QVector>
#include <QTimer>
#include "notificationserver.h"
class Notification;
class NotificationsModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
IdRole = Qt::UserRole + 1,
SummaryRole = Qt::DisplayRole,
ImageRole = Qt::DecorationRole,
CreatedRole,
UpdatedRole,
BodyRole,
IconNameRole,
};
Q_ENUM(Roles)
explicit NotificationsModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void close(uint id);
int rowOfNotification(uint id) const;
void removeRows(const QVector<int> &rows);
private slots:
void onNotificationAdded(const Notification &notification);
void onNotificationReplaced(uint replacedId, const Notification &notification);
void onNotificationRemoved(uint notificationId, NotificationServer::CloseReason reason);
private:
QVector<Notification> m_notifications;
QVector<uint /*notificationId*/> m_pendingRemovals;
QTimer m_pendingRemovalTimer;
};
#endif // NOTIFICATIONSMODEL_H

@ -0,0 +1,69 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!--
Based on the output of command:
qdbus org.freedesktop.Notifications /org/freedesktop/Notifications org.freedesktop.DBus.Introspectable.Introspect
against xfce4-notifyd
-->
<node>
<!--interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="data" direction="out" type="s"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg name="interface" direction="in" type="s"/>
<arg name="propname" direction="in" type="s"/>
<arg name="value" direction="out" type="v"/>
</method>
<method name="Set">
<arg name="interface" direction="in" type="s"/>
<arg name="propname" direction="in" type="s"/>
<arg name="value" direction="in" type="v"/>
</method>
<method name="GetAll">
<arg name="interface" direction="in" type="s"/>
<arg name="props" direction="out" type="a{sv}"/>
</method>
</interface>
<interface name="org.xfce.Notifyd">
<method name="Quit">
</method>
</interface-->
<interface name="org.freedesktop.Notifications">
<method name="GetServerInformation">
<arg name="name" type="s" direction="out"/>
<arg name="vendor" type="s" direction="out"/>
<arg name="version" type="s" direction="out"/>
<arg name="spec_version" type="s" direction="out"/>
</method>
<method name="CloseNotification">
<arg name="id" type="u" direction="in"/>
</method>
<method name="Notify">
<annotation name="org.qtproject.QtDBus.QtTypeName.In6" value="QVariantMap"/>
<arg name="app_name" type="s" direction="in"/>
<arg name="replaces_id" type="u" direction="in"/>
<arg name="app_icon" type="s" direction="in"/>
<arg name="summary" type="s" direction="in"/>
<arg name="body" type="s" direction="in"/>
<arg name="actions" type="as" direction="in"/>
<arg name="hints" type="a{sv}" direction="in"/>
<arg name="expire_timeout" type="i" direction="in"/>
<arg name="id" type="u" direction="out"/>
</method>
<method name="GetCapabilities">
<arg name="capabilities" type="as" direction="out"/>
</method>
<signal name="ActionInvoked">
<arg type="u"/>
<arg type="s"/>
</signal>
<signal name="NotificationClosed">
<arg type="u"/>
<arg type="u"/>
</signal>
</interface>
</node>

@ -0,0 +1,131 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: Reion Wong <reion@cutefishos.com>
*
* 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
* 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/>.
*/
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
import QtGraphicalEffects 1.0
import FishUI 1.0 as FishUI
import Cutefish.Notification 1.0
Window {
id: control
flags: Qt.WindowDoesNotAcceptFocus | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Popup
width: 400
height: 70
color: "transparent"
visible: false
onVisibleChanged: if (visible) timer.restart()
FishUI.WindowShadow {
view: control
radius: _background.radius
}
FishUI.WindowBlur {
view: control
geometry: Qt.rect(control.x, control.y, control.width, control.height)
windowRadius: _background.radius
enabled: true
}
Rectangle {
id: _background
anchors.fill: parent
radius: height * 0.2
color: FishUI.Theme.backgroundColor
opacity: 0.5
}
MouseArea {
id: _mouseArea
z: 999
anchors.fill: parent
onClicked: notificationsModel.close(model.notificationId)
hoverEnabled: true
onEntered: timer.stop()
onExited: timer.restart()
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: FishUI.Units.largeSpacing
anchors.rightMargin: FishUI.Units.smallSpacing
anchors.topMargin: FishUI.Units.smallSpacing
anchors.bottomMargin: FishUI.Units.smallSpacing
spacing: FishUI.Units.largeSpacing
Image {
id: _icon
width: 48
height: width
source: "image://icontheme/%1".arg(model.iconName)
sourceSize: Qt.size(width, height)
Layout.alignment: Qt.AlignVCenter
}
ColumnLayout {
spacing: 0
Item {
Layout.fillHeight: true
}
Label {
text: model.summary
visible: text
elide: Text.ElideRight
Layout.fillWidth: true
}
Label {
text: model.body
visible: text
elide: Text.ElideRight
Layout.fillWidth: true
}
Item {
Layout.fillHeight: true
}
}
Image {
width: 24
height: 24
source: "qrc:/images/" + (FishUI.Theme.darkMode ? "dark" : "light") + "/close.svg"
sourceSize: Qt.size(width, height)
visible: _mouseArea.containsMouse
Layout.alignment: Qt.AlignTop
}
}
Timer {
id: timer
interval: 5000
onTriggered: {
notificationsModel.close(model.notificationId)
}
}
}

@ -0,0 +1,90 @@
/*
SPDX-FileCopyrightText: 2021 Reion Wong <reion@cutefishos.com>
SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.12
import QtQml 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
import QtGraphicalEffects 1.0
import FishUI 1.0 as FishUI
import Cutefish.Notification 1.0
Item {
id: root
width: 0
height: 0
visible: false
property int popupEdgeDistance: FishUI.Units.largeSpacing
property int popupSpacing: FishUI.Units.largeSpacing
readonly property real popupMaximumScreenFill: 0.8
readonly property rect screenRect: {
let rect = Qt.rect(screen.screenGeometry.x + screen.availableScreenRect.x,
screen.screenGeometry.y + screen.availableScreenRect.y,
screen.availableScreenRect.width,
screen.availableScreenRect.height)
return rect
}
property Instantiator popupInstantiator: Instantiator {
model: notificationsModel
delegate: NotificationPopup {}
onObjectAdded: {
positionPopups()
}
onObjectRemoved: {
positionPopups()
}
}
ScreenHelper {
id: screen
}
NotificationsModel {
id: notificationsModel
}
function positionPopups() {
const screenRect = root.screenRect
if (screenRect.width <= 0 || screenRect.height <= 0) {
return
}
let y = screenRect.y
y += root.popupEdgeDistance
for (var i = 0; i < popupInstantiator.count; ++i) {
let popup = popupInstantiator.objectAt(i)
if (!popup)
continue
var popupEffectiveWidth = popup.width
const leftMostX = screenRect.x + root.popupEdgeDistance
const rightMostX = screenRect.x + screenRect.width - root.popupEdgeDistance - popupEffectiveWidth
// right
popup.x = rightMostX
popup.y = y
y += popup.height + (popup.height > 0 ? popupSpacing : 0)
// don't let notifications take more than popupMaximumScreenFill of the screen
var visible = true
if (i > 0) { // however always show at least one popup
visible = (popup.y + popup.height < screenRect.y + (screenRect.height * popupMaximumScreenFill))
}
popup.visible = visible
}
}
}

@ -0,0 +1,8 @@
<RCC>
<qresource prefix="/">
<file>qml/main.qml</file>
<file>qml/NotificationPopup.qml</file>
<file>images/dark/close.svg</file>
<file>images/light/close.svg</file>
</qresource>
</RCC>

@ -0,0 +1,39 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: Reion Wong <reion@cutefishos.com>
*
* 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
* 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 "screenhelper.h"
#include <QApplication>
#include <QScreen>
ScreenHelper::ScreenHelper(QObject *parent)
: QObject(parent)
{
connect(qApp->primaryScreen(), &QScreen::geometryChanged, this, &ScreenHelper::screenGeometryChanged);
connect(qApp->primaryScreen(), &QScreen::availableGeometryChanged, this, &ScreenHelper::availableScreenRectChanged);
}
QRect ScreenHelper::screenGeometry() const
{
return qApp->primaryScreen()->geometry();
}
QRect ScreenHelper::availableScreenRect() const
{
return qApp->primaryScreen()->availableGeometry();
}

@ -0,0 +1,43 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: Reion Wong <reion@cutefishos.com>
*
* 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
* 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 SCREENHELPER_H
#define SCREENHELPER_H
#include <QObject>
#include <QRect>
class ScreenHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(QRect screenGeometry READ screenGeometry NOTIFY screenGeometryChanged)
Q_PROPERTY(QRect availableScreenRect READ availableScreenRect NOTIFY availableScreenRectChanged)
public:
explicit ScreenHelper(QObject *parent = nullptr);
QRect screenGeometry() const;
QRect availableScreenRect() const;
signals:
void screenGeometryChanged();
void availableScreenRectChanged();
};
#endif // SCREENHELPER_H

@ -0,0 +1,16 @@
#include "view.h"
#include <QApplication>
#include <QQmlContext>
View::View(QQuickView *parent)
: QQuickView(parent)
{
rootContext()->setContextProperty("View", this);
setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
// setResizeMode(QQuickView::SizeViewToRootObject);
setSource(QUrl("qrc:/qml/main.qml"));
setScreen(qApp->primaryScreen());
setColor(Qt::transparent);
}

@ -0,0 +1,15 @@
#ifndef VIEW_H
#define VIEW_H
#include <QQuickView>
class View : public QQuickView
{
Q_OBJECT
public:
explicit View(QQuickView *parent = nullptr);
};
#endif // VIEW_H

@ -118,6 +118,7 @@ void ProcessManager::startDesktopProcess()
QList<QPair<QString, QStringList>> list;
// Desktop components
list << qMakePair(QString("cutefish-notificationd"), QStringList());
list << qMakePair(QString("cutefish-statusbar"), QStringList());
list << qMakePair(QString("cutefish-dock"), QStringList());
list << qMakePair(QString("cutefish-filemanager"), QStringList("--desktop"));

Loading…
Cancel
Save