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;
}
//--------------------------------------------------------------------------------