mirror of https://github.com/qTox/qTox.git
Browse Source
qTox added specialized tray icon backends over time to work around bugs with the default implementation, since then our GTK+2 backend has stopped working, our appindicator backend was never selected by default by cmake, and statusnotifier was never selected at SystemTrayIcon construction, leaving us only ever using the broken GTK+2 backend, or theoretically the Unity backend, which I didn't see selected on Ubuntu 16.04 Unity. In all other cases we would fall back to the Qt backend. Qt icon has improved over time, and our platform specific icons have become stale, with GTK+2 becoming deprecated and GTK+3 not having a similar feature, QSystemTrayIcon has been tested on a variety of DEs and works as well or better in all cases, as shown in the table at https://github.com/qTox/qTox/issues/5881#issuecomment-541892457 Fix #5881 Fix #5859reviewable/pr5888/r2
7 changed files with 3 additions and 567 deletions
@ -1,335 +0,0 @@
@@ -1,335 +0,0 @@
|
||||
/*
|
||||
Copyright © 2015-2019 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre 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. |
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
|
||||
#include "systemtrayicon.h" |
||||
#include "src/persistence/settings.h" |
||||
#include <QDebug> |
||||
#include <QFile> |
||||
#include <QMenu> |
||||
#include <QString> |
||||
#include <QSystemTrayIcon> |
||||
|
||||
SystemTrayIcon::SystemTrayIcon() |
||||
{ |
||||
QString desktop = getenv("XDG_CURRENT_DESKTOP"); |
||||
if (desktop.isEmpty()) |
||||
desktop = getenv("DESKTOP_SESSION"); |
||||
desktop = desktop.toLower(); |
||||
if (false) |
||||
; |
||||
#ifdef ENABLE_SYSTRAY_UNITY_BACKEND |
||||
else if (desktop == "unity") { |
||||
qDebug() << "Using Unity backend"; |
||||
gtk_init(nullptr, nullptr); |
||||
QString settingsDir = Settings::getInstance().getSettingsDirPath(); |
||||
QFile iconFile(settingsDir + "/icon.png"); |
||||
if (iconFile.open(QIODevice::Truncate | QIODevice::WriteOnly)) { |
||||
QFile resIconFile(":/img/icon.png"); |
||||
if (resIconFile.open(QIODevice::ReadOnly)) |
||||
iconFile.write(resIconFile.readAll()); |
||||
resIconFile.close(); |
||||
iconFile.close(); |
||||
} |
||||
backendType = SystrayBackendType::Unity; |
||||
unityMenu = gtk_menu_new(); |
||||
const std::string settingsDirString = settingsDir.toStdString(); |
||||
unityIndicator = |
||||
app_indicator_new_with_path("qTox", "icon", APP_INDICATOR_CATEGORY_APPLICATION_STATUS, |
||||
settingsDirString.c_str()); |
||||
app_indicator_set_menu(unityIndicator, GTK_MENU(unityMenu)); |
||||
} |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_GTK_BACKEND |
||||
else if (desktop == "xfce" || desktop.contains("gnome") || desktop == "mate" |
||||
|| desktop == "x-cinnamon") { |
||||
qDebug() << "Using GTK backend"; |
||||
backendType = SystrayBackendType::GTK; |
||||
gtk_init(nullptr, nullptr); |
||||
|
||||
gtkIcon = gtk_status_icon_new(); |
||||
|
||||
gtkMenu = gtk_menu_new(); |
||||
|
||||
void (*callbackTrigger)(GtkStatusIcon*, gpointer) = [](GtkStatusIcon*, gpointer data) { |
||||
static_cast<SystemTrayIcon*>(data)->activated(QSystemTrayIcon::Trigger); |
||||
}; |
||||
g_signal_connect(gtkIcon, "activate", G_CALLBACK(callbackTrigger), this); |
||||
void (*callbackButtonClick)(GtkStatusIcon*, GdkEvent*, |
||||
gpointer) = [](GtkStatusIcon*, GdkEvent* event, gpointer data) { |
||||
if (event->button.button == 2) |
||||
static_cast<SystemTrayIcon*>(data)->activated(QSystemTrayIcon::MiddleClick); |
||||
}; |
||||
g_signal_connect(gtkIcon, "button-release-event", G_CALLBACK(callbackButtonClick), this); |
||||
} |
||||
#endif |
||||
else { |
||||
qDebug() << "Using the Qt backend"; |
||||
qtIcon = new QSystemTrayIcon; |
||||
backendType = SystrayBackendType::Qt; |
||||
connect(qtIcon, &QSystemTrayIcon::activated, this, &SystemTrayIcon::activated); |
||||
} |
||||
} |
||||
|
||||
SystemTrayIcon::~SystemTrayIcon() |
||||
{ |
||||
qDebug() << "Deleting SystemTrayIcon"; |
||||
delete qtIcon; |
||||
} |
||||
|
||||
QString SystemTrayIcon::extractIconToFile(QIcon icon, QString name) |
||||
{ |
||||
QString iconPath; |
||||
(void)icon; |
||||
(void)name; |
||||
#ifdef ENABLE_SYSTRAY_UNITY_BACKEND |
||||
iconPath = Settings::getInstance().getSettingsDirPath() + "/" + name + ".png"; |
||||
QSize iconSize = icon.actualSize(QSize{64, 64}); |
||||
icon.pixmap(iconSize).save(iconPath); |
||||
#endif |
||||
return iconPath; |
||||
} |
||||
|
||||
#if defined(ENABLE_SYSTRAY_GTK_BACKEND) || defined(ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND) |
||||
GdkPixbuf* SystemTrayIcon::convertQIconToPixbuf(const QIcon& icon) |
||||
{ |
||||
void (*callbackFreeImage)(guchar*, gpointer) = [](guchar* image_bytes, gpointer) { |
||||
delete[] image_bytes; |
||||
}; |
||||
QImage image = icon.pixmap(64, 64).toImage(); |
||||
if (image.format() != QImage::Format_RGBA8888_Premultiplied) |
||||
image = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied); |
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) |
||||
guchar* image_bytes = new guchar[image.sizeInBytes()]; |
||||
memcpy(image_bytes, image.bits(), image.sizeInBytes()); |
||||
#else |
||||
guchar* image_bytes = new guchar[image.byteCount()]; |
||||
memcpy(image_bytes, image.bits(), image.byteCount()); |
||||
#endif |
||||
return gdk_pixbuf_new_from_data(image_bytes, GDK_COLORSPACE_RGB, image.hasAlphaChannel(), 8, |
||||
image.width(), image.height(), image.bytesPerLine(), |
||||
callbackFreeImage, nullptr); |
||||
} |
||||
#endif |
||||
|
||||
void SystemTrayIcon::setContextMenu(QMenu* menu) |
||||
{ |
||||
if (false) |
||||
; |
||||
#ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND |
||||
else if (backendType == SystrayBackendType::StatusNotifier) { |
||||
void (*callbackClick)(StatusNotifier*, gint, gint, gpointer) = [](StatusNotifier*, gint, |
||||
gint, gpointer data) { |
||||
static_cast<SystemTrayIcon*>(data)->activated(QSystemTrayIcon::Trigger); |
||||
}; |
||||
g_signal_connect(statusNotifier, "activate", G_CALLBACK(callbackClick), this); |
||||
void (*callbackMiddleClick)(StatusNotifier*, gint, gint, |
||||
gpointer) = [](StatusNotifier*, gint, gint, gpointer data) { |
||||
static_cast<SystemTrayIcon*>(data)->activated(QSystemTrayIcon::MiddleClick); |
||||
}; |
||||
g_signal_connect(statusNotifier, "secondary_activate", G_CALLBACK(callbackMiddleClick), this); |
||||
|
||||
for (QAction* a : menu->actions()) { |
||||
const std::string aText = a->text().replace('&', "").toStdString(); |
||||
GtkWidget* item; |
||||
if (a->isSeparator()) |
||||
item = gtk_menu_item_new(); |
||||
else if (a->icon().isNull()) |
||||
item = gtk_menu_item_new_with_label(aText.c_str()); |
||||
else { |
||||
GdkPixbuf* pixbuf = convertQIconToPixbuf(a->icon()); |
||||
item = gtk_image_menu_item_new_with_label(aText.c_str()); |
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), |
||||
gtk_image_new_from_pixbuf(pixbuf)); |
||||
gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE); |
||||
g_object_unref(pixbuf); |
||||
} |
||||
gtk_menu_shell_append(GTK_MENU_SHELL(snMenu), item); |
||||
void (*callback)(GtkMenu*, gpointer data) = [](GtkMenu*, gpointer a) { |
||||
((QAction*)a)->activate(QAction::Trigger); |
||||
}; |
||||
g_signal_connect(item, "activate", G_CALLBACK(callback), a); |
||||
gtk_widget_show(item); |
||||
} |
||||
void (*callbackMenu)(StatusNotifier*, gint, gint, gpointer) = [](StatusNotifier*, gint, |
||||
gint, gpointer data) { |
||||
gtk_widget_show_all(static_cast<SystemTrayIcon*>(data)->snMenu); |
||||
gtk_menu_popup(GTK_MENU(static_cast<SystemTrayIcon*>(data)->snMenu), 0, 0, 0, 0, 3, |
||||
gtk_get_current_event_time()); |
||||
}; |
||||
g_signal_connect(statusNotifier, "context-menu", G_CALLBACK(callbackMenu), this); |
||||
} |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_GTK_BACKEND |
||||
else if (backendType == SystrayBackendType::GTK) { |
||||
for (QAction* a : menu->actions()) { |
||||
const std::string aText = a->text().replace('&', "").toStdString(); |
||||
GtkWidget* item; |
||||
if (a->isSeparator()) |
||||
item = gtk_menu_item_new(); |
||||
else if (a->icon().isNull()) |
||||
item = gtk_menu_item_new_with_label(aText.c_str()); |
||||
else { |
||||
GdkPixbuf* pixbuf = convertQIconToPixbuf(a->icon()); |
||||
item = gtk_image_menu_item_new_with_label(aText.c_str()); |
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), |
||||
gtk_image_new_from_pixbuf(pixbuf)); |
||||
gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE); |
||||
g_object_unref(pixbuf); |
||||
} |
||||
gtk_menu_shell_append(GTK_MENU_SHELL(gtkMenu), item); |
||||
void (*callback)(GtkMenu*, gpointer data) = [](GtkMenu*, gpointer a) { |
||||
((QAction*)a)->activate(QAction::Trigger); |
||||
}; |
||||
g_signal_connect(item, "activate", G_CALLBACK(callback), a); |
||||
gtk_widget_show(item); |
||||
} |
||||
void (*callbackMenu)(GtkMenu*, gint, gint, gpointer) = [](GtkMenu*, gint, gint, gpointer data) { |
||||
gtk_widget_show_all(static_cast<SystemTrayIcon*>(data)->gtkMenu); |
||||
gtk_menu_popup(GTK_MENU(static_cast<SystemTrayIcon*>(data)->gtkMenu), 0, 0, 0, 0, 3, |
||||
gtk_get_current_event_time()); |
||||
}; |
||||
g_signal_connect(gtkIcon, "popup-menu", G_CALLBACK(callbackMenu), this); |
||||
} |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_UNITY_BACKEND |
||||
else if (backendType == SystrayBackendType::Unity) { |
||||
for (QAction* a : menu->actions()) { |
||||
const std::string aText = a->text().replace('&', "").toStdString(); |
||||
GtkWidget* item; |
||||
if (a->isSeparator()) |
||||
item = gtk_menu_item_new(); |
||||
else if (a->icon().isNull()) |
||||
item = gtk_menu_item_new_with_label(aText.c_str()); |
||||
else { |
||||
const std::string iconPath = |
||||
extractIconToFile(a->icon(), "iconmenu" + a->icon().name()).toStdString(); |
||||
GtkWidget* image = gtk_image_new_from_file(iconPath.c_str()); |
||||
item = gtk_image_menu_item_new_with_label(aText.c_str()); |
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image); |
||||
gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE); |
||||
} |
||||
gtk_menu_shell_append(GTK_MENU_SHELL(unityMenu), item); |
||||
void (*callback)(GtkMenu*, gpointer data) = [](GtkMenu*, gpointer a) { |
||||
static_cast<QAction*>(a)->activate(QAction::Trigger); |
||||
}; |
||||
g_signal_connect(item, "activate", G_CALLBACK(callback), a); |
||||
gtk_widget_show(item); |
||||
} |
||||
app_indicator_set_menu(unityIndicator, GTK_MENU(unityMenu)); |
||||
DbusmenuServer* menuServer; |
||||
DbusmenuMenuitem* rootMenuItem; |
||||
g_object_get(unityIndicator, "dbus-menu-server", &menuServer, nullptr); |
||||
g_object_get(menuServer, "root-node", &rootMenuItem, nullptr); |
||||
void (*callback)(DbusmenuMenuitem*, gpointer) = [](DbusmenuMenuitem*, gpointer data) { |
||||
static_cast<SystemTrayIcon*>(data)->activated(QSystemTrayIcon::Unknown); |
||||
}; |
||||
g_signal_connect(rootMenuItem, "about-to-show", G_CALLBACK(callback), this); |
||||
} |
||||
#endif |
||||
else if (backendType == SystrayBackendType::Qt) { |
||||
qtIcon->setContextMenu(menu); |
||||
} |
||||
} |
||||
|
||||
void SystemTrayIcon::show() |
||||
{ |
||||
setVisible(true); |
||||
} |
||||
|
||||
void SystemTrayIcon::hide() |
||||
{ |
||||
setVisible(false); |
||||
} |
||||
|
||||
void SystemTrayIcon::setVisible(bool newState) |
||||
{ |
||||
if (false) |
||||
; |
||||
#ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND |
||||
else if (backendType == SystrayBackendType::StatusNotifier) { |
||||
if (newState) |
||||
status_notifier_set_status(statusNotifier, STATUS_NOTIFIER_STATUS_ACTIVE); |
||||
else |
||||
status_notifier_set_status(statusNotifier, STATUS_NOTIFIER_STATUS_PASSIVE); |
||||
} |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_GTK_BACKEND |
||||
else if (backendType == SystrayBackendType::GTK) { |
||||
if (newState) |
||||
gtk_status_icon_set_visible(gtkIcon, true); |
||||
else |
||||
gtk_status_icon_set_visible(gtkIcon, false); |
||||
} |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_UNITY_BACKEND |
||||
else if (backendType == SystrayBackendType::Unity) { |
||||
if (newState) |
||||
app_indicator_set_status(unityIndicator, APP_INDICATOR_STATUS_ACTIVE); |
||||
else |
||||
app_indicator_set_status(unityIndicator, APP_INDICATOR_STATUS_PASSIVE); |
||||
} |
||||
#endif |
||||
else if (backendType == SystrayBackendType::Qt) { |
||||
if (newState) |
||||
qtIcon->show(); |
||||
else |
||||
qtIcon->hide(); |
||||
} |
||||
} |
||||
|
||||
void SystemTrayIcon::setIcon(QIcon& icon) |
||||
{ |
||||
if (false) |
||||
; |
||||
#ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND |
||||
else if (backendType == SystrayBackendType::StatusNotifier) { |
||||
GdkPixbuf* pixbuf = convertQIconToPixbuf(icon); |
||||
status_notifier_set_from_pixbuf(statusNotifier, STATUS_NOTIFIER_ICON, pixbuf); |
||||
g_object_unref(pixbuf); |
||||
} |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_GTK_BACKEND |
||||
else if (backendType == SystrayBackendType::GTK) { |
||||
GdkPixbuf* pixbuf = convertQIconToPixbuf(icon); |
||||
gtk_status_icon_set_from_pixbuf(gtkIcon, pixbuf); |
||||
g_object_unref(pixbuf); |
||||
} |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_UNITY_BACKEND |
||||
else if (backendType == SystrayBackendType::Unity) { |
||||
// Alternate file names or appindicator will not reload the icon
|
||||
if (app_indicator_get_icon(unityIndicator) == QString("icon2")) { |
||||
extractIconToFile(icon, "icon"); |
||||
app_indicator_set_icon_full(unityIndicator, "icon", "qtox"); |
||||
} else { |
||||
extractIconToFile(icon, "icon2"); |
||||
app_indicator_set_icon_full(unityIndicator, "icon2", "qtox"); |
||||
} |
||||
} |
||||
#endif |
||||
else if (backendType == SystrayBackendType::Qt) { |
||||
qtIcon->setIcon(icon); |
||||
} |
||||
} |
||||
|
||||
SystrayBackendType SystemTrayIcon::backend() const |
||||
{ |
||||
return backendType; |
||||
} |
@ -1,70 +0,0 @@
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright © 2015-2019 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre 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. |
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
|
||||
#ifndef SYSTEMTRAYICON_H |
||||
#define SYSTEMTRAYICON_H |
||||
|
||||
#include "systemtrayicon_private.h" |
||||
#include <QObject> |
||||
|
||||
class QSystemTrayIcon; |
||||
class QMenu; |
||||
|
||||
class SystemTrayIcon : public QObject |
||||
{ |
||||
Q_OBJECT |
||||
public: |
||||
SystemTrayIcon(); |
||||
~SystemTrayIcon(); |
||||
void setContextMenu(QMenu* menu); |
||||
void show(); |
||||
void hide(); |
||||
void setVisible(bool); |
||||
void setIcon(QIcon& icon); |
||||
SystrayBackendType backend() const; |
||||
|
||||
signals: |
||||
void activated(QSystemTrayIcon::ActivationReason); |
||||
|
||||
private: |
||||
QString extractIconToFile(QIcon icon, QString name = "icon"); |
||||
#if defined(ENABLE_SYSTRAY_GTK_BACKEND) || defined(ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND) |
||||
static GdkPixbuf* convertQIconToPixbuf(const QIcon& icon); |
||||
#endif |
||||
|
||||
private: |
||||
SystrayBackendType backendType; |
||||
QSystemTrayIcon* qtIcon = nullptr; |
||||
|
||||
#ifdef ENABLE_SYSTRAY_UNITY_BACKEND |
||||
AppIndicator* unityIndicator; |
||||
GtkWidget* unityMenu; |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND |
||||
StatusNotifier* statusNotifier; |
||||
GtkWidget* snMenu; |
||||
#endif |
||||
#ifdef ENABLE_SYSTRAY_GTK_BACKEND |
||||
GtkStatusIcon* gtkIcon; |
||||
GtkWidget* gtkMenu; |
||||
#endif |
||||
}; |
||||
|
||||
#endif // SYSTEMTRAYICON_H
|
@ -1,71 +0,0 @@
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
Copyright © 2015-2019 by The qTox Project Contributors |
||||
|
||||
This file is part of qTox, a Qt-based graphical interface for Tox. |
||||
|
||||
qTox is libre 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. |
||||
|
||||
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
|
||||
#ifndef SYSTEMTRAYICON_PRIVATE_H |
||||
#define SYSTEMTRAYICON_PRIVATE_H |
||||
|
||||
#include <QSystemTrayIcon> |
||||
|
||||
#ifdef ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND |
||||
#ifdef signals |
||||
#undef signals |
||||
#endif |
||||
extern "C" { |
||||
#include "src/platform/statusnotifier/statusnotifier.h" |
||||
#include <gdk-pixbuf/gdk-pixbuf.h> |
||||
#include <gio/gio.h> |
||||
#include <glib-object.h> |
||||
#include <glib.h> |
||||
#include <gtk/gtk.h> |
||||
} |
||||
#define signals public |
||||
#endif |
||||
|
||||
#ifdef ENABLE_SYSTRAY_UNITY_BACKEND |
||||
#ifdef signals |
||||
#undef signals |
||||
#endif |
||||
extern "C" { |
||||
#include <gtk/gtk.h> |
||||
#include <libappindicator/app-indicator.h> |
||||
#include <libdbusmenu-glib/server.h> |
||||
} |
||||
#define signals public |
||||
#endif |
||||
|
||||
#ifdef ENABLE_SYSTRAY_GTK_BACKEND |
||||
#ifdef signals |
||||
#undef signals |
||||
#endif |
||||
extern "C" { |
||||
#include <gtk/gtk.h> |
||||
} |
||||
#define signals public |
||||
#endif |
||||
|
||||
enum class SystrayBackendType |
||||
{ |
||||
Qt, |
||||
Unity, |
||||
StatusNotifier, |
||||
GTK |
||||
}; |
||||
|
||||
#endif // SYSTEMTRAYICON_PRIVATE_H
|
Loading…
Reference in new issue