Get rid of custom SNI implementation

XDG is inventing new tray specification, so SNI will be outdated soon and it's better to just use QSystemTrayIcon.
I believe all the major drawbacks of QSystemTrayIcon are solved and we can live with minor ones.
Given the planned MainWindow refactoring, it seems it's the best time to do that.
This commit is contained in:
Ilya Fedin 2022-01-21 20:51:43 +04:00 committed by John Preston
parent aed49b9289
commit b65d40a22b
9 changed files with 33 additions and 1139 deletions

View File

@ -1334,7 +1334,6 @@ else()
if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
target_link_libraries(Telegram
PRIVATE
desktop-app::external_statusnotifieritem
desktop-app::external_dbusmenu_qt
desktop-app::external_glibmm
desktop-app::external_glib

View File

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "base/platform/base_platform_info.h"
#include "base/event_filter.h"
#include "base/unique_qptr.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/input_fields.h"
#include "ui/ui_utility.h"
@ -41,7 +42,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
#include <QtCore/QSize>
#include <QtCore/QTemporaryFile>
#include <QtCore/QMimeData>
#include <QtGui/QWindow>
@ -49,9 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QDBusObjectPath>
#include <QtDBus/QDBusMetaType>
#include <statusnotifieritem.h>
#include <dbusmenuexporter.h>
#include <glibmm.h>
@ -69,13 +67,6 @@ constexpr auto kMutePanelTrayIconName = "telegram-mute-panel"_cs;
constexpr auto kAttentionPanelTrayIconName = "telegram-attention-panel"_cs;
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
constexpr auto kTrayIconFilename = "tdesktop-trayicon-XXXXXX.png"_cs;
constexpr auto kSNIWatcherService = "org.kde.StatusNotifierWatcher"_cs;
constexpr auto kSNIWatcherObjectPath = "/StatusNotifierWatcher"_cs;
constexpr auto kSNIWatcherInterface = kSNIWatcherService;
constexpr auto kAppMenuService = "com.canonical.AppMenu.Registrar"_cs;
constexpr auto kAppMenuObjectPath = "/com/canonical/AppMenu/Registrar"_cs;
constexpr auto kAppMenuInterface = kAppMenuService;
@ -390,88 +381,6 @@ void ForceDisabled(QAction *action, bool disabled) {
}
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
bool IsIndicatorApplication() {
// Hack for indicator-application,
// which doesn't handle icons sent across D-Bus:
// save the icon to a temp file
// and set the icon name to that filename.
static const auto Result = [] {
try {
const auto connection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
const auto ubuntuIndicator = base::Platform::DBus::NameHasOwner(
connection,
"com.canonical.indicator.application");
const auto ayatanaIndicator = base::Platform::DBus::NameHasOwner(
connection,
"org.ayatana.indicator.application");
return ubuntuIndicator || ayatanaIndicator;
} catch (...) {
}
return false;
}();
return Result;
}
std::unique_ptr<QTemporaryFile> TrayIconFile(
const QIcon &icon,
QObject *parent = nullptr) {
static const auto templateName = AppRuntimeDirectory()
+ kTrayIconFilename.utf16();
static const auto dprSize = [](const QPixmap &pixmap) {
return pixmap.size() / pixmap.devicePixelRatio();
};
static const auto desiredSize = QSize(22, 22);
static const auto scalePixmap = [=](const QPixmap &pixmap) {
if (dprSize(pixmap) != desiredSize) {
return pixmap.scaled(
desiredSize * pixmap.devicePixelRatio(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
} else {
return pixmap;
}
};
auto ret = std::make_unique<QTemporaryFile>(
templateName,
parent);
ret->open();
const auto firstAttempt = icon.pixmap(desiredSize);
const auto firstAttemptSize = dprSize(firstAttempt);
if (firstAttemptSize.width() < desiredSize.width()) {
const auto availableSizes = icon.availableSizes();
const auto biggestSize = ranges::max_element(
availableSizes,
std::less<>(),
&QSize::width);
if (biggestSize->width() > firstAttemptSize.width()) {
scalePixmap(icon.pixmap(*biggestSize)).save(ret.get());
} else {
scalePixmap(firstAttempt).save(ret.get());
}
} else {
scalePixmap(firstAttempt).save(ret.get());
}
ret->close();
return ret;
}
bool UseUnityCounter() {
static const auto Result = [&] {
try {
@ -490,54 +399,6 @@ bool UseUnityCounter() {
return Result;
}
bool IsSNIAvailable() {
try {
const auto connection = [] {
try {
return Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
} catch (...) {
return Glib::RefPtr<Gio::DBus::Connection>();
}
}();
if (!connection) {
return false;
}
auto reply = connection->call_sync(
std::string(kSNIWatcherObjectPath),
std::string(kPropertiesInterface),
"Get",
base::Platform::MakeGlibVariant(std::tuple{
Glib::ustring(std::string(kSNIWatcherInterface)),
Glib::ustring("IsStatusNotifierHostRegistered"),
}),
std::string(kSNIWatcherService));
return base::Platform::GlibVariantCast<bool>(
base::Platform::GlibVariantCast<Glib::VariantBase>(
reply.get_child(0)));
} catch (const Glib::Error &e) {
static const auto NotSupportedErrors = {
"org.freedesktop.DBus.Error.ServiceUnknown",
};
const auto errorName = Gio::DBus::ErrorUtils::get_remote_error(e);
if (ranges::contains(NotSupportedErrors, errorName)) {
return false;
}
LOG(("SNI Error: %1")
.arg(QString::fromStdString(e.what())));
} catch (const std::exception &e) {
LOG(("SNI Error: %1")
.arg(QString::fromStdString(e.what())));
}
return false;
}
uint djbStringHash(const std::string &string) {
uint hash = 5381;
for (const auto &curChar : string) {
@ -608,162 +469,50 @@ void UnregisterAppMenu(QWindow *window) {
} // namespace
class MainWindow::Private {
class MainWindow::Private : public QObject {
public:
explicit Private(not_null<MainWindow*> window)
: _public(window) {
QCoreApplication::instance()->installEventFilter(this);
}
base::unique_qptr<Ui::PopupMenu> trayIconMenuXEmbed;
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
Glib::RefPtr<Gio::DBus::Connection> dbusConnection;
StatusNotifierItem *sniTrayIcon = nullptr;
uint sniRegisteredSignalId = 0;
uint sniWatcherId = 0;
std::unique_ptr<QTemporaryFile> trayIconFile;
bool appMenuSupported = false;
uint appMenuWatcherId = 0;
DBusMenuExporter *mainMenuExporter = nullptr;
void setSNITrayIcon(int counter, bool muted);
void attachToSNITrayIcon();
void handleSNIHostRegistered();
void handleSNIOwnerChanged(
const QString &service,
const QString &oldOwner,
const QString &newOwner);
void handleAppMenuOwnerChanged(
const QString &service,
const QString &oldOwner,
const QString &newOwner);
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
protected:
bool eventFilter(QObject *obj, QEvent *e) override;
private:
not_null<MainWindow*> _public;
};
bool MainWindow::Private::eventFilter(QObject *obj, QEvent *e) {
if (obj->objectName() == qstr("QSystemTrayIconSys")
&& e->type() == QEvent::MouseButtonPress) {
const auto ee = static_cast<QMouseEvent*>(e);
if (ee->button() == Qt::RightButton) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
_public->handleTrayIconActication(QSystemTrayIcon::Context);
});
return true;
}
}
return QObject::eventFilter(obj, e);
}
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
void MainWindow::Private::setSNITrayIcon(int counter, bool muted) {
const auto iconName = GetTrayIconName(counter, muted);
const auto panelIconName = GetPanelIconName(counter, muted);
if (iconName == panelIconName) {
if (sniTrayIcon->iconName() == iconName) {
return;
}
sniTrayIcon->setIconByName(iconName);
sniTrayIcon->setToolTipIconByName(iconName);
} else if (IsIndicatorApplication()) {
if (!IsIconRegenerationNeeded(counter, muted)
&& trayIconFile
&& sniTrayIcon->iconName() == trayIconFile->fileName()) {
return;
}
const auto icon = TrayIconGen(counter, muted);
trayIconFile = TrayIconFile(icon, _public);
if (trayIconFile) {
// indicator-application doesn't support tooltips
sniTrayIcon->setIconByName(trayIconFile->fileName());
}
} else {
if (!IsIconRegenerationNeeded(counter, muted)
&& !sniTrayIcon->iconPixmap().isEmpty()
&& sniTrayIcon->iconName().isEmpty()) {
return;
}
const auto icon = TrayIconGen(counter, muted);
sniTrayIcon->setIconByPixmap(icon);
sniTrayIcon->setToolTipIconByPixmap(icon);
}
}
void MainWindow::Private::attachToSNITrayIcon() {
sniTrayIcon->setToolTipTitle(AppName.utf16());
connect(sniTrayIcon,
&StatusNotifierItem::activateRequested,
[=](const QPoint &) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
_public->handleTrayIconActication(QSystemTrayIcon::Trigger);
});
});
connect(sniTrayIcon,
&StatusNotifierItem::secondaryActivateRequested,
[=](const QPoint &) {
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
_public->handleTrayIconActication(QSystemTrayIcon::MiddleClick);
});
});
}
void MainWindow::Private::handleSNIHostRegistered() {
if (_public->_sniAvailable) {
return;
}
_public->_sniAvailable = true;
if (Core::App().settings().workMode() == WorkMode::WindowOnly) {
return;
}
LOG(("Switching to SNI tray icon..."));
if (_public->trayIcon) {
_public->trayIcon->setContextMenu(nullptr);
_public->trayIcon->deleteLater();
}
_public->trayIcon = nullptr;
_public->psSetupTrayIcon();
SkipTaskbar(
_public->windowHandle(),
Core::App().settings().workMode() == WorkMode::TrayOnly);
}
void MainWindow::Private::handleSNIOwnerChanged(
const QString &service,
const QString &oldOwner,
const QString &newOwner) {
_public->_sniAvailable = IsSNIAvailable();
if (Core::App().settings().workMode() == WorkMode::WindowOnly) {
return;
}
if (oldOwner.isEmpty() && !newOwner.isEmpty() && _public->_sniAvailable) {
LOG(("Switching to SNI tray icon..."));
} else if (!oldOwner.isEmpty() && newOwner.isEmpty()) {
LOG(("Switching to Qt tray icon..."));
} else {
return;
}
if (_public->trayIcon) {
_public->trayIcon->setContextMenu(0);
_public->trayIcon->deleteLater();
}
_public->trayIcon = nullptr;
if (_public->trayAvailable()) {
_public->psSetupTrayIcon();
} else {
LOG(("System tray is not available."));
}
SkipTaskbar(
_public->windowHandle(),
(Core::App().settings().workMode() == WorkMode::TrayOnly)
&& _public->trayAvailable());
}
void MainWindow::Private::handleAppMenuOwnerChanged(
const QString &service,
const QString &oldOwner,
@ -787,11 +536,6 @@ void MainWindow::Private::handleAppMenuOwnerChanged(
MainWindow::MainWindow(not_null<Window::Controller*> controller)
: Window::MainWindow(controller)
, _private(std::make_unique<Private>(this)) {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
qDBusRegisterMetaType<ToolTip>();
qDBusRegisterMetaType<IconPixmap>();
qDBusRegisterMetaType<IconPixmapList>();
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
}
void MainWindow::initHook() {
@ -817,47 +561,12 @@ void MainWindow::initHook() {
});
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
_sniAvailable = IsSNIAvailable();
_private->appMenuSupported = IsAppMenuSupported();
try {
_private->dbusConnection = Gio::DBus::Connection::get_sync(
Gio::DBus::BusType::BUS_TYPE_SESSION);
_private->sniRegisteredSignalId = _private->dbusConnection->signal_subscribe(
[](
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender_name,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &signal_name,
const Glib::VariantContainerBase &parameters) {
if (signal_name == "StatusNotifierHostRegistered") {
crl::on_main([] {
if (const auto window = App::wnd()) {
window->_private->handleSNIHostRegistered();
}
});
}
},
std::string(kSNIWatcherService),
std::string(kSNIWatcherInterface),
"StatusNotifierHostRegistered",
std::string(kSNIWatcherObjectPath));
_private->sniWatcherId = base::Platform::DBus::RegisterServiceWatcher(
_private->dbusConnection,
std::string(kSNIWatcherService),
[=](
const Glib::ustring &service,
const Glib::ustring &oldOwner,
const Glib::ustring &newOwner) {
_private->handleSNIOwnerChanged(
QString::fromStdString(service),
QString::fromStdString(oldOwner),
QString::fromStdString(newOwner));
});
_private->appMenuWatcherId = base::Platform::DBus::RegisterServiceWatcher(
_private->dbusConnection,
std::string(kAppMenuService),
@ -890,15 +599,11 @@ void MainWindow::initHook() {
XCBSetDesktopFileName(windowHandle());
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
LOG(("System tray available: %1").arg(Logs::b(trayAvailable())));
LOG(("System tray available: %1").arg(Logs::b(TrayIconSupported())));
}
bool MainWindow::hasTrayIcon() const {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
return trayIcon || (_sniAvailable && _private->sniTrayIcon);
#else
return trayIcon;
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
}
bool MainWindow::isActiveForTrayMenu() {
@ -907,44 +612,19 @@ bool MainWindow::isActiveForTrayMenu() {
}
void MainWindow::psShowTrayMenu() {
_trayIconMenuXEmbed->popup(QCursor::pos());
_private->trayIconMenuXEmbed->popup(QCursor::pos());
}
void MainWindow::psTrayMenuUpdated() {
}
void MainWindow::psSetupTrayIcon() {
const auto counter = Core::App().unreadBadge();
const auto muted = Core::App().unreadBadgeMuted();
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (_sniAvailable) {
LOG(("Using SNI tray icon."));
if (!_private->sniTrayIcon) {
_private->sniTrayIcon = new StatusNotifierItem(
QCoreApplication::applicationName(),
this);
_private->sniTrayIcon->setTitle(AppName.utf16());
_private->sniTrayIcon->setCategory(qsl("Communications"));
_private->sniTrayIcon->setContextMenu(trayIconMenu);
_private->setSNITrayIcon(counter, muted);
_private->attachToSNITrayIcon();
}
updateIconCounters();
return;
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
LOG(("Using Qt tray icon."));
if (!trayIcon) {
trayIcon = new QSystemTrayIcon(this);
if (_sniAvailable) {
trayIcon->setContextMenu(trayIconMenu);
}
trayIcon->setIcon(TrayIconGen(counter, muted));
trayIcon->setContextMenu(trayIconMenu);
trayIcon->setIcon(TrayIconGen(
Core::App().unreadBadge(),
Core::App().unreadBadgeMuted()));
attachToTrayIcon(trayIcon);
}
@ -954,17 +634,9 @@ void MainWindow::psSetupTrayIcon() {
}
void MainWindow::workmodeUpdated(Core::Settings::WorkMode mode) {
if (!trayAvailable()) {
if (!TrayIconSupported()) {
return;
} else if (mode == WorkMode::WindowOnly) {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (_private->sniTrayIcon) {
_private->sniTrayIcon->setContextMenu(0);
_private->sniTrayIcon->deleteLater();
}
_private->sniTrayIcon = nullptr;
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (trayIcon) {
trayIcon->setContextMenu(0);
trayIcon->deleteLater();
@ -1024,10 +696,6 @@ void MainWindow::updateIconCounters() {
} catch (...) {
}
}
if (_private->sniTrayIcon) {
_private->setSNITrayIcon(counter, muted);
}
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (trayIcon && IsIconRegenerationNeeded(counter, muted)) {
@ -1036,8 +704,8 @@ void MainWindow::updateIconCounters() {
}
void MainWindow::initTrayMenuHook() {
_trayIconMenuXEmbed.emplace(nullptr, trayIconMenu);
_trayIconMenuXEmbed->deleteOnHide(false);
_private->trayIconMenuXEmbed.emplace(nullptr, trayIconMenu);
_private->trayIconMenuXEmbed->deleteOnHide(false);
}
void MainWindow::createGlobalMenu() {
@ -1310,7 +978,7 @@ void MainWindow::handleNativeSurfaceChanged(bool exist) {
SkipTaskbar(
windowHandle(),
(Core::App().settings().workMode() == WorkMode::TrayOnly)
&& trayAvailable());
&& TrayIconSupported());
}
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
@ -1327,16 +995,6 @@ void MainWindow::handleNativeSurfaceChanged(bool exist) {
MainWindow::~MainWindow() {
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
if (_private->dbusConnection) {
if (_private->sniRegisteredSignalId != 0) {
_private->dbusConnection->signal_unsubscribe(
_private->sniRegisteredSignalId);
}
if (_private->sniWatcherId != 0) {
_private->dbusConnection->signal_unsubscribe(
_private->sniWatcherId);
}
if (_private->appMenuWatcherId != 0) {
_private->dbusConnection->signal_unsubscribe(
_private->appMenuWatcherId);

View File

@ -8,11 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "platform/platform_main_window.h"
#include "base/unique_qptr.h"
namespace Ui {
class PopupMenu;
} // namespace Ui
namespace Platform {
@ -22,10 +17,6 @@ public:
void psShowTrayMenu();
bool trayAvailable() {
return _sniAvailable || QSystemTrayIcon::isSystemTrayAvailable();
}
bool isActiveForTrayMenu() override;
~MainWindow();
@ -52,9 +43,6 @@ private:
friend class Private;
const std::unique_ptr<Private> _private;
bool _sniAvailable = false;
base::unique_qptr<Ui::PopupMenu> _trayIconMenuXEmbed;
QMenu *psMainMenu = nullptr;
QAction *psLogout = nullptr;
QAction *psUndo = nullptr;

View File

@ -592,9 +592,7 @@ bool AutostartSkip() {
}
bool TrayIconSupported() {
return App::wnd()
? App::wnd()->trayAvailable()
: false;
return QSystemTrayIcon::isSystemTrayAvailable();
}
bool SkipTaskbarSupported() {

View File

@ -1,75 +0,0 @@
/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXQt - a lightweight, Qt based, desktop toolset
* https://lxqt.org
*
* Copyright: 2015 LXQt team
* Authors:
* Balázs Béla <balazsbela[at]gmail.com>
* Paulo Lieuthier <paulolieuthier@gmail.com>
*
* This program or library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* END_COMMON_COPYRIGHT_HEADER */
#include "dbustypes.h"
// Marshall the IconPixmap data into a D-Bus argument
QDBusArgument &operator<<(QDBusArgument &argument, const IconPixmap &icon)
{
argument.beginStructure();
argument << icon.width;
argument << icon.height;
argument << icon.bytes;
argument.endStructure();
return argument;
}
// Retrieve the ImageStruct data from the D-Bus argument
const QDBusArgument &operator>>(const QDBusArgument &argument, IconPixmap &icon)
{
argument.beginStructure();
argument >> icon.width;
argument >> icon.height;
argument >> icon.bytes;
argument.endStructure();
return argument;
}
// Marshall the ToolTip data into a D-Bus argument
QDBusArgument &operator<<(QDBusArgument &argument, const ToolTip &toolTip)
{
argument.beginStructure();
argument << toolTip.iconName;
argument << toolTip.iconPixmap;
argument << toolTip.title;
argument << toolTip.description;
argument.endStructure();
return argument;
}
// Retrieve the ToolTip data from the D-Bus argument
const QDBusArgument &operator>>(const QDBusArgument &argument, ToolTip &toolTip)
{
argument.beginStructure();
argument >> toolTip.iconName;
argument >> toolTip.iconPixmap;
argument >> toolTip.title;
argument >> toolTip.description;
argument.endStructure();
return argument;
}

View File

@ -1,60 +0,0 @@
/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXQt - a lightweight, Qt based, desktop toolset
* https://lxqt.org
*
* Copyright: 2015 LXQt team
* Authors:
* Balázs Béla <balazsbela[at]gmail.com>
* Paulo Lieuthier <paulolieuthier@gmail.com>
*
* This program or library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* END_COMMON_COPYRIGHT_HEADER */
#include <QDBusArgument>
#ifndef DBUSTYPES_H
#define DBUSTYPES_H
struct IconPixmap {
int width;
int height;
QByteArray bytes;
};
typedef QList<IconPixmap> IconPixmapList;
Q_DECLARE_METATYPE(IconPixmap)
Q_DECLARE_METATYPE(IconPixmapList)
struct ToolTip {
QString iconName;
QList<IconPixmap> iconPixmap;
QString title;
QString description;
};
Q_DECLARE_METATYPE(ToolTip)
QDBusArgument &operator<<(QDBusArgument &argument, const IconPixmap &icon);
const QDBusArgument &operator>>(const QDBusArgument &argument, IconPixmap &icon);
QDBusArgument &operator<<(QDBusArgument &argument, const ToolTip &toolTip);
const QDBusArgument &operator>>(const QDBusArgument &argument, ToolTip &toolTip);
#endif // DBUSTYPES_H

View File

@ -1,69 +0,0 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierItem">
<property name="Category" type="s" access="read"/>
<property name="Id" type="s" access="read"/>
<property name="Title" type="s" access="read"/>
<property name="Status" type="s" access="read"/>
<property name="WindowId" type="i" access="read"/>
<property name="IconThemePath" type="s" access="read"/>
<property name="Menu" type="o" access="read"/>
<property name="ItemIsMenu" type="b" access="read"/>
<property name="IconName" type="s" access="read"/>
<property name="IconPixmap" type="a(iiay)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="IconPixmapList"/>
</property>
<property name="OverlayIconName" type="s" access="read"/>
<property name="OverlayIconPixmap" type="a(iiay)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="IconPixmapList"/>
</property>
<property name="AttentionIconName" type="s" access="read"/>
<property name="AttentionIconPixmap" type="a(iiay)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="IconPixmapList"/>
</property>
<property name="AttentionMovieName" type="s" access="read"/>
<property name="ToolTip" type="(sa(iiay)ss)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="ToolTip"/>
</property>
<method name="ContextMenu">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Activate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="SecondaryActivate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Scroll">
<arg name="delta" type="i" direction="in"/>
<arg name="orientation" type="s" direction="in"/>
</method>
<signal name="NewTitle">
</signal>
<signal name="NewIcon">
</signal>
<signal name="NewAttentionIcon">
</signal>
<signal name="NewOverlayIcon">
</signal>
<signal name="NewToolTip">
</signal>
<signal name="NewStatus">
<arg name="status" type="s"/>
</signal>
</interface>
</node>

View File

@ -1,354 +0,0 @@
/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXQt - a lightweight, Qt based, desktop toolset
* https://lxqt.org/
*
* Copyright: 2015 LXQt team
* Authors:
* Paulo Lieuthier <paulolieuthier@gmail.com>
*
* This program or library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* END_COMMON_COPYRIGHT_HEADER */
#include "statusnotifieritem.h"
#include "statusnotifieritemadaptor.h"
#include <QDBusServiceWatcher>
#include <QDBusMessage>
#include <utility>
#include <dbusmenuexporter.h>
int StatusNotifierItem::mServiceCounter = 0;
StatusNotifierItem::StatusNotifierItem(QString id, QObject *parent)
: QObject(parent),
mAdaptor(new StatusNotifierItemAdaptor(this)),
mService(QString::fromLatin1("org.freedesktop.StatusNotifierItem-%1-%2")
.arg(QCoreApplication::applicationPid())
.arg(++mServiceCounter)),
mId(std::move(id)),
mTitle(QLatin1String("Test")),
mStatus(QLatin1String("Active")),
mCategory(QLatin1String("ApplicationStatus")),
mMenu(nullptr),
mMenuPath(QLatin1String("/NO_DBUSMENU")),
mMenuExporter(nullptr),
mSessionBus(QDBusConnection::connectToBus(QDBusConnection::SessionBus, mService))
{
// Separate DBus connection to the session bus is created, because QDbus does not provide
// a way to register different objects for different services with the same paths.
// For status notifiers we need different /StatusNotifierItem for each service.
// register service
mSessionBus.registerObject(QLatin1String("/StatusNotifierItem"), this);
registerToHost();
// monitor the watcher service in case the host restarts
QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QLatin1String("org.kde.StatusNotifierWatcher"),
mSessionBus,
QDBusServiceWatcher::WatchForOwnerChange,
this);
connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged,
this, &StatusNotifierItem::onServiceOwnerChanged);
}
StatusNotifierItem::~StatusNotifierItem()
{
mSessionBus.unregisterObject(QLatin1String("/StatusNotifierItem"));
QDBusConnection::disconnectFromBus(mService);
}
void StatusNotifierItem::registerToHost()
{
QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.kde.StatusNotifierWatcher"),
QLatin1String("/StatusNotifierWatcher"),
QLatin1String("org.kde.StatusNotifierWatcher"),
QLatin1String("RegisterStatusNotifierItem"));
message.setArguments({
mSessionBus.baseService()
});
mSessionBus.send(message);
}
void StatusNotifierItem::onServiceOwnerChanged(const QString& service, const QString& oldOwner,
const QString& newOwner)
{
Q_UNUSED(service);
Q_UNUSED(oldOwner);
if (!newOwner.isEmpty())
registerToHost();
}
void StatusNotifierItem::onMenuDestroyed()
{
mMenu = nullptr;
setMenuPath(QLatin1String("/NO_DBUSMENU"));
mMenuExporter = nullptr; //mMenu is a QObject parent of the mMenuExporter
}
void StatusNotifierItem::setTitle(const QString &title)
{
if (mTitle == title)
return;
mTitle = title;
Q_EMIT mAdaptor->NewTitle();
}
void StatusNotifierItem::setStatus(const QString &status)
{
if (mStatus == status)
return;
mStatus = status;
Q_EMIT mAdaptor->NewStatus(mStatus);
}
void StatusNotifierItem::setCategory(const QString &category)
{
if (mCategory == category)
return;
mCategory = category;
}
void StatusNotifierItem::setMenuPath(const QString& path)
{
mMenuPath.setPath(path);
}
void StatusNotifierItem::setIconByName(const QString &name)
{
if (mIconName == name)
return;
mIconName = name;
Q_EMIT mAdaptor->NewIcon();
}
void StatusNotifierItem::setIconByPixmap(const QIcon &icon)
{
if (mIconCacheKey == icon.cacheKey())
return;
mIconCacheKey = icon.cacheKey();
mIcon = iconToPixmapList(icon);
mIconName.clear();
Q_EMIT mAdaptor->NewIcon();
}
void StatusNotifierItem::setOverlayIconByName(const QString &name)
{
if (mOverlayIconName == name)
return;
mOverlayIconName = name;
Q_EMIT mAdaptor->NewOverlayIcon();
}
void StatusNotifierItem::setOverlayIconByPixmap(const QIcon &icon)
{
if (mOverlayIconCacheKey == icon.cacheKey())
return;
mOverlayIconCacheKey = icon.cacheKey();
mOverlayIcon = iconToPixmapList(icon);
mOverlayIconName.clear();
Q_EMIT mAdaptor->NewOverlayIcon();
}
void StatusNotifierItem::setAttentionIconByName(const QString &name)
{
if (mAttentionIconName == name)
return;
mAttentionIconName = name;
Q_EMIT mAdaptor->NewAttentionIcon();
}
void StatusNotifierItem::setAttentionIconByPixmap(const QIcon &icon)
{
if (mAttentionIconCacheKey == icon.cacheKey())
return;
mAttentionIconCacheKey = icon.cacheKey();
mAttentionIcon = iconToPixmapList(icon);
mAttentionIconName.clear();
Q_EMIT mAdaptor->NewAttentionIcon();
}
void StatusNotifierItem::setToolTipTitle(const QString &title)
{
if (mTooltipTitle == title)
return;
mTooltipTitle = title;
Q_EMIT mAdaptor->NewToolTip();
}
void StatusNotifierItem::setToolTipSubTitle(const QString &subTitle)
{
if (mTooltipSubtitle == subTitle)
return;
mTooltipSubtitle = subTitle;
Q_EMIT mAdaptor->NewToolTip();
}
void StatusNotifierItem::setToolTipIconByName(const QString &name)
{
if (mTooltipIconName == name)
return;
mTooltipIconName = name;
Q_EMIT mAdaptor->NewToolTip();
}
void StatusNotifierItem::setToolTipIconByPixmap(const QIcon &icon)
{
if (mTooltipIconCacheKey == icon.cacheKey())
return;
mTooltipIconCacheKey = icon.cacheKey();
mTooltipIcon = iconToPixmapList(icon);
mTooltipIconName.clear();
Q_EMIT mAdaptor->NewToolTip();
}
void StatusNotifierItem::setContextMenu(QMenu* menu)
{
if (mMenu == menu)
return;
if (nullptr != mMenu)
{
disconnect(mMenu, &QObject::destroyed, this, &StatusNotifierItem::onMenuDestroyed);
}
mMenu = menu;
if (nullptr != mMenu)
setMenuPath(QLatin1String("/MenuBar"));
else
setMenuPath(QLatin1String("/NO_DBUSMENU"));
//Note: we need to destroy menu exporter before creating new one -> to free the DBus object path for new menu
delete mMenuExporter;
if (nullptr != mMenu)
{
connect(mMenu, &QObject::destroyed, this, &StatusNotifierItem::onMenuDestroyed);
mMenuExporter = new DBusMenuExporter{this->menu().path(), mMenu, mSessionBus};
}
}
void StatusNotifierItem::Activate(int x, int y)
{
if (mStatus == QLatin1String("NeedsAttention"))
mStatus = QLatin1String("Active");
Q_EMIT activateRequested(QPoint(x, y));
}
void StatusNotifierItem::SecondaryActivate(int x, int y)
{
if (mStatus == QLatin1String("NeedsAttention"))
mStatus = QLatin1String("Active");
Q_EMIT secondaryActivateRequested(QPoint(x, y));
}
void StatusNotifierItem::ContextMenu(int x, int y)
{
if (mMenu != nullptr)
{
if (mMenu->isVisible())
mMenu->popup(QPoint(x, y));
else
mMenu->hide();
}
}
void StatusNotifierItem::Scroll(int delta, const QString &orientation)
{
Qt::Orientation orient = Qt::Vertical;
if (orientation.toLower() == QLatin1String("horizontal"))
orient = Qt::Horizontal;
Q_EMIT scrollRequested(delta, orient);
}
void StatusNotifierItem::showMessage(const QString& title, const QString& msg,
const QString& iconName, int secs)
{
QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.Notifications"),
QLatin1String("/org/freedesktop/Notifications"),
QLatin1String("org.freedesktop.Notifications"),
QLatin1String("Notify"));
message.setArguments({
mTitle,
(uint) 0,
iconName,
title,
msg,
QStringList(),
QVariantMap(),
secs
});
mSessionBus.send(message);
}
IconPixmapList StatusNotifierItem::iconToPixmapList(const QIcon& icon)
{
IconPixmapList pixmapList;
// long live KDE!
const QList<QSize> sizes = icon.availableSizes();
for (const QSize &size : sizes)
{
QImage image = icon.pixmap(size).toImage();
IconPixmap pix;
pix.height = image.height();
pix.width = image.width();
if (image.format() != QImage::Format_ARGB32)
image = image.convertToFormat(QImage::Format_ARGB32);
pix.bytes = QByteArray((char *) image.bits(), image.sizeInBytes());
// swap to network byte order if we are little endian
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)
{
quint32 *uintBuf = (quint32 *) pix.bytes.data();
for (uint i = 0; i < pix.bytes.size() / sizeof(quint32); ++i)
{
*uintBuf = qToBigEndian(*uintBuf);
++uintBuf;
}
}
pixmapList.append(pix);
}
return pixmapList;
}

View File

@ -1,191 +0,0 @@
/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXQt - a lightweight, Qt based, desktop toolset
* https://lxqt.org/
*
* Copyright: 2015 LXQt team
* Authors:
* Paulo Lieuthier <paulolieuthier@gmail.com>
*
* This program or library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* END_COMMON_COPYRIGHT_HEADER */
#ifndef STATUS_NOTIFIER_ITEM_H
#define STATUS_NOTIFIER_ITEM_H
#include <QObject>
#include <QIcon>
#include <QMenu>
#include <QDBusConnection>
#include "dbustypes.h"
class StatusNotifierItemAdaptor;
class DBusMenuExporter;
class StatusNotifierItem : public QObject
{
Q_OBJECT
Q_PROPERTY(QString Category READ category)
Q_PROPERTY(QString Title READ title)
Q_PROPERTY(QString Id READ id)
Q_PROPERTY(QString Status READ status)
Q_PROPERTY(QDBusObjectPath Menu READ menu)
Q_PROPERTY(QString IconName READ iconName)
Q_PROPERTY(IconPixmapList IconPixmap READ iconPixmap)
Q_PROPERTY(QString OverlayIconName READ overlayIconName)
Q_PROPERTY(IconPixmapList OverlayIconPixmap READ overlayIconPixmap)
Q_PROPERTY(QString AttentionIconName READ attentionIconName)
Q_PROPERTY(IconPixmapList AttentionIconPixmap READ attentionIconPixmap)
Q_PROPERTY(ToolTip ToolTip READ toolTip)
public:
StatusNotifierItem(QString id, QObject *parent = nullptr);
~StatusNotifierItem() override;
QString id() const
{ return mId; }
QString title() const
{ return mTitle; }
void setTitle(const QString &title);
QString status() const
{ return mStatus; }
void setStatus(const QString &status);
QString category() const
{ return mCategory; }
void setCategory(const QString &category);
QDBusObjectPath menu() const
{ return mMenuPath; }
void setMenuPath(const QString &path);
QString iconName() const
{ return mIconName; }
void setIconByName(const QString &name);
IconPixmapList iconPixmap() const
{ return mIcon; }
void setIconByPixmap(const QIcon &icon);
QString overlayIconName() const
{ return mOverlayIconName; }
void setOverlayIconByName(const QString &name);
IconPixmapList overlayIconPixmap() const
{ return mOverlayIcon; }
void setOverlayIconByPixmap(const QIcon &icon);
QString attentionIconName() const
{ return mAttentionIconName; }
void setAttentionIconByName(const QString &name);
IconPixmapList attentionIconPixmap() const
{ return mAttentionIcon; }
void setAttentionIconByPixmap(const QIcon &icon);
QString toolTipTitle() const
{ return mTooltipTitle; }
void setToolTipTitle(const QString &title);
QString toolTipSubTitle() const
{ return mTooltipSubtitle; }
void setToolTipSubTitle(const QString &subTitle);
QString toolTipIconName() const
{ return mTooltipIconName; }
void setToolTipIconByName(const QString &name);
IconPixmapList toolTipIconPixmap() const
{ return mTooltipIcon; }
void setToolTipIconByPixmap(const QIcon &icon);
ToolTip toolTip() const
{
ToolTip tt;
tt.title = mTooltipTitle;
tt.description = mTooltipSubtitle;
tt.iconName = mTooltipIconName;
tt.iconPixmap = mTooltipIcon;
return tt;
}
/*!
* \Note: we don't take ownership for the \param menu
*/
void setContextMenu(QMenu *menu);
public Q_SLOTS:
void Activate(int x, int y);
void SecondaryActivate(int x, int y);
void ContextMenu(int x, int y);
void Scroll(int delta, const QString &orientation);
void showMessage(const QString &title, const QString &msg, const QString &iconName, int secs);
private:
void registerToHost();
IconPixmapList iconToPixmapList(const QIcon &icon);
private Q_SLOTS:
void onServiceOwnerChanged(const QString &service, const QString &oldOwner,
const QString &newOwner);
void onMenuDestroyed();
Q_SIGNALS:
void activateRequested(const QPoint &pos);
void secondaryActivateRequested(const QPoint &pos);
void scrollRequested(int delta, Qt::Orientation orientation);
private:
StatusNotifierItemAdaptor *mAdaptor;
QString mService;
QString mId;
QString mTitle;
QString mStatus;
QString mCategory;
// icons
QString mIconName, mOverlayIconName, mAttentionIconName;
IconPixmapList mIcon, mOverlayIcon, mAttentionIcon;
qint64 mIconCacheKey, mOverlayIconCacheKey, mAttentionIconCacheKey;
// tooltip
QString mTooltipTitle, mTooltipSubtitle, mTooltipIconName;
IconPixmapList mTooltipIcon;
qint64 mTooltipIconCacheKey;
// menu
QMenu *mMenu;
QDBusObjectPath mMenuPath;
DBusMenuExporter *mMenuExporter;
QDBusConnection mSessionBus;
static int mServiceCounter;
};
#endif