diff --git a/NotificationList.cxx b/NotificationList.cxx index b93ca00..8b59d40 100644 --- a/NotificationList.cxx +++ b/NotificationList.cxx @@ -1,316 +1,316 @@ /* Copyright 2017,2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const Qt::WindowFlags POPUP_FLAGS = Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint; //-------------------------------------------------------------------------------- NotifyItem::NotifyItem(QWidget *parent, uint theId, const QString &app, const QString &summary, const QString &body, const QIcon &icon, const QStringList &actions) : QFrame(parent, POPUP_FLAGS), id(theId), appName(app) { setAttribute(Qt::WA_ShowWithoutActivating); // avoid focus stealing setFrameShape(QFrame::StyledPanel); QMargins margins = contentsMargins(); margins.setRight(0); // allow the close button to reach to the right screen border - easier to click setContentsMargins(margins); QVBoxLayout *vbox = new QVBoxLayout; timeLabel = new QLabel; iconLabel = new QLabel; vbox->addWidget(timeLabel, 0, Qt::AlignTop | Qt::AlignHCenter); vbox->addWidget(iconLabel, 0, Qt::AlignTop | Qt::AlignHCenter); QVBoxLayout *centerBox = new QVBoxLayout; QHBoxLayout *hbox = new QHBoxLayout(this); margins = hbox->contentsMargins(); margins.setRight(0); // allow the close button to reach to the right screen border - easier to click hbox->setContentsMargins(margins); textLabel = new QLabel; QToolButton *closeButton = new QToolButton; // easier to click with larger size closeButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); closeButton->setMinimumWidth(40); closeButton->setAutoRaise(true); closeButton->setIcon(QIcon::fromTheme("window-close")); connect(closeButton, &QToolButton::clicked, this, &NotifyItem::deleteLater); textLabel->setWordWrap(true); textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); textLabel->setOpenExternalLinks(true); textLabel->setMinimumWidth(300); textLabel->setMaximumWidth(600); textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); centerBox->addWidget(textLabel); if ( actions.count() ) { QHBoxLayout *actionBox = new QHBoxLayout; centerBox->addLayout(actionBox); for (int i = 0; i < actions.count(); i++) { - if ( (i % 2) != 0 ) // id + if ( ((i % 2) != 0) && !actions[i].isEmpty() ) // id { QPushButton *button = new QPushButton; button->setText(actions[i]); actionBox->addWidget(button); QString key = actions[i - 1]; connect(button, &QPushButton::clicked, this, [this, key]() { QDBusMessage msg = QDBusMessage::createSignal("/org/freedesktop/Notifications", "org.freedesktop.Notifications", "ActionInvoked"); msg << id << key; QDBusConnection::sessionBus().send(msg); }); } } } hbox->addLayout(vbox); hbox->addLayout(centerBox); hbox->addWidget(closeButton); iconLabel->setFixedSize(32, 32); iconLabel->setPixmap(icon.pixmap(iconLabel->size())); QString title = (appName == summary) ? appName : (appName + ": " + summary); textLabel->setText(QString("
%1

%2") .arg(title) .arg(body)); timeLabel->setText(QTime::currentTime().toString(Qt::SystemLocaleShortDate)); } //-------------------------------------------------------------------------------- void NotifyItem::destroySysResources() { destroy(); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- NotificationList::NotificationList(QWidget *parent) : QWidget(parent) { setWindowFlags(windowFlags() | Qt::Tool); setWindowTitle(i18n("Notifications")); scrollArea = new QScrollArea; scrollArea->setWidgetResizable(true); QWidget *listWidget = new QWidget; listVbox = new QVBoxLayout(listWidget); listVbox->setContentsMargins(QMargins()); listVbox->addStretch(); scrollArea->setWidget(listWidget); QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setContentsMargins(QMargins()); vbox->addWidget(scrollArea); QPushButton *clearButton = new QPushButton; clearButton->setIcon(QIcon::fromTheme("edit-clear-list")); connect(clearButton, &QPushButton::clicked, this, [this]() { for (NotifyItem *item : items) item->deleteLater(); }); vbox->addWidget(clearButton); resize(500, 300); } //-------------------------------------------------------------------------------- NotificationList::~NotificationList() { for (NotifyItem *item : items) item->disconnect(); // make sure destroyed() is no longer triggered } //-------------------------------------------------------------------------------- void NotificationList::addItem(uint id, const QString &appName, const QString &summary, const QString &body, const QIcon &icon, const QStringList &actions, const QVariantMap &hints, int timeout) { QPointer item = new NotifyItem(nullptr, id, appName, summary, body, icon, actions); item->resize(500, item->sizeHint().height()); KWindowSystem::setState(item->winId(), NET::SkipTaskbar | NET::SkipPager); items.append(item.data()); placeItems(); int wordCount = body.splitRef(' ', QString::SkipEmptyParts).count(); bool neverExpires = timeout == 0; // according to spec if ( timeout <= 0 ) { timeout = 4000 + 250 * wordCount; if ( actions.count() ) timeout += 15000; // give user more time to think ... } // 0=low, 1=normal, 2=critical if ( hints.contains("urgency") && (hints["urgency"].toInt() == 2) ) { neverExpires = true; timeout = 20000; // just show it longer since its urgent } bool transient = hints.contains("transient") ? hints["transient"].toBool() : false; // if there are actions, show it longer if ( actions.count() || neverExpires ) transient = false; // exceptions ... if ( transient && hints.contains("x-kde-eventId") ) { // I'd like to keep this much longer if ( hints["x-kde-eventId"].toString() == "new-email" ) transient = false; } numItems++; emit itemsCountChanged(); connect(item.data(), &NotifyItem::destroyed, this, [this](QObject *obj) { items.removeOne(static_cast(obj)); placeItems(); if ( --numItems == 0 ) { hide(); emit listNowEmpty(); } else emit itemsCountChanged(); } ); QTimer::singleShot(timeout, [=]() { if ( item ) { // sometimes there were some leftover/half-functioning windows. This seems to avoid that item->destroySysResources(); listVbox->insertWidget(listVbox->count() - 1, item); // insert before stretch item->show(); placeItems(); // reorder the remaining ones scrollArea->setWidgetResizable(true); // to update scrollbars, else next line does not work scrollArea->ensureWidgetVisible(item); if ( !neverExpires ) { if ( !appTimeouts.contains(appName) ) appTimeouts.insert(appName, 10); // default: 10 minutes lifetime int expireTimeout; if ( transient ) expireTimeout = 2 * 60 * 1000; // still keep it a short time else expireTimeout = appTimeouts[appName] * 60 * 1000; QTimer::singleShot(expireTimeout, item.data(), &NotifyItem::deleteLater); } } } ); } //-------------------------------------------------------------------------------- void NotificationList::closeItem(uint id) { for (NotifyItem *item : items) { if ( item->getId() == id ) { item->deleteLater(); break; } } } //-------------------------------------------------------------------------------- void NotificationList::placeItems() { QRect screen = QApplication::desktop()->availableGeometry(this); QPoint point = parentWidget()->mapToGlobal(parentWidget()->pos()); int x = point.x(); int y = screen.bottom(); for (NotifyItem *item : items) { if ( !item->parentWidget() ) // temporary item not in the list yet { y -= item->sizeHint().height(); y -= 5; // a small space point.setX(std::min(x, screen.x() + screen.width() - item->sizeHint().width())); point.setY(y); item->move(point); item->show(); } } } //-------------------------------------------------------------------------------- diff --git a/NotificationServer.cxx b/NotificationServer.cxx index 1a553cf..63b3fbb 100644 --- a/NotificationServer.cxx +++ b/NotificationServer.cxx @@ -1,116 +1,132 @@ /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include +#include //-------------------------------------------------------------------------------- NotificationServer::NotificationServer(QWidget *parent) : SysTrayItem(parent, "preferences-desktop-notification") { new NotificationsAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); if ( dbus.registerService("org.freedesktop.Notifications") ) { if ( !dbus.registerObject("/org/freedesktop/Notifications", this) ) dbus.unregisterService("org.freedesktop.Notifications"); } notificationList = new NotificationList(this); connect(notificationList, &NotificationList::listNowEmpty, this, &NotificationServer::hide); connect(notificationList, &NotificationList::itemsCountChanged, [this]() { show(); setToolTip(i18np("%1 notification", "%1 notifications", notificationList->itemCount())); } ); hide(); } //-------------------------------------------------------------------------------- QStringList NotificationServer::GetCapabilities() { return QStringList() << "body" << "body-hyperlinks" << "body-images" << "body-markup" << "icon-static" << "persistence" << "actions" ; } //-------------------------------------------------------------------------------- void NotificationServer::CloseNotification(uint id) { notificationList->closeItem(id); } //-------------------------------------------------------------------------------- QString NotificationServer::GetServerInformation(QString &vendor, QString &version, QString &spec_version) { vendor = "kollix"; version = "1.0"; spec_version = "1.2"; return vendor; } //-------------------------------------------------------------------------------- uint NotificationServer::Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &theBody, const QStringList &actions, const QVariantMap &hints, int timeout) { //qDebug() << "app" << app_name << "summary" << summary << "body" << theBody << "timeout" << timeout << "replaceId" << replaces_id // << "hints" << hints << "actions" << actions; QString body(theBody); body.replace("\n", "
"); - notificationList->addItem(notifyId, app_name, summary, body, QIcon::fromTheme(app_icon), actions, hints, timeout); + + QIcon icon; + if ( !app_icon.isEmpty() ) + icon = QIcon::fromTheme(app_icon); + else if ( hints.contains("image-path") ) + icon = QIcon(hints["image-path"].toString()); + + QString appName = app_name; + if ( appName.isEmpty() && hints.contains("desktop-entry") ) + { + KService::Ptr service = KService::serviceByDesktopName(hints["desktop-entry"].toString().toLower()); + if ( service ) + appName = service->name(); + } + + notificationList->addItem(notifyId, appName, summary, body, icon, actions, hints, timeout); if ( replaces_id != 0 ) notificationList->closeItem(replaces_id); return notifyId++; } //-------------------------------------------------------------------------------- QWidget *NotificationServer::getDetailsList() { return notificationList; } //--------------------------------------------------------------------------------