From 3b4dfa138176b4d1e3cb14be1be638ebe2f52713 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 31 Jan 2020 10:34:37 +0400 Subject: [PATCH] Use LXQt's StatusNotifierItem implementation instead of appindicator --- .github/workflows/linux.yml | 2 +- .gitmodules | 3 + Telegram/CMakeLists.txt | 30 +- Telegram/SourceFiles/mainwindow.cpp | 18 +- .../linux/linux_desktop_environment.cpp | 31 +- .../linux/linux_desktop_environment.h | 14 +- .../SourceFiles/platform/linux/linux_libs.cpp | 47 +- .../SourceFiles/platform/linux/linux_libs.h | 17 - .../platform/linux/main_window_linux.cpp | 631 +++++++----------- .../platform/linux/main_window_linux.h | 38 +- .../platform/linux/specific_linux.cpp | 75 ++- .../platform/linux/specific_linux.h | 2 + .../platform/mac/main_window_mac.mm | 3 + Telegram/ThirdParty/libdbusmenu-qt | 1 + .../statusnotifieritem/dbustypes.cpp | 75 +++ .../ThirdParty/statusnotifieritem/dbustypes.h | 60 ++ .../org.kde.StatusNotifierItem.xml | 69 ++ .../statusnotifieritem/statusnotifieritem.cpp | 331 +++++++++ .../statusnotifieritem/statusnotifieritem.h | 185 +++++ Telegram/cmake/telegram_options.cmake | 6 +- docs/building-cmake.md | 2 +- snap/snapcraft.yaml | 16 +- 22 files changed, 1089 insertions(+), 567 deletions(-) create mode 160000 Telegram/ThirdParty/libdbusmenu-qt create mode 100644 Telegram/ThirdParty/statusnotifieritem/dbustypes.cpp create mode 100644 Telegram/ThirdParty/statusnotifieritem/dbustypes.h create mode 100644 Telegram/ThirdParty/statusnotifieritem/org.kde.StatusNotifierItem.xml create mode 100644 Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.cpp create mode 100644 Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.h diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9bd4aec99..02727c532 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -54,7 +54,7 @@ jobs: sudo apt-get update sudo apt-get install software-properties-common -y && \ sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev \ - libappindicator-dev libicu-dev libdee-dev libdrm-dev dh-autoreconf \ + libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \ autoconf automake build-essential libass-dev libfreetype6-dev \ libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev \ libvorbis-dev libenchant-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev \ diff --git a/.gitmodules b/.gitmodules index 2190249af..b96eac1b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -61,3 +61,6 @@ [submodule "Telegram/lib_qr"] path = Telegram/lib_qr url = https://github.com/desktop-app/lib_qr.git +[submodule "Telegram/ThirdParty/libdbusmenu-qt"] + path = Telegram/ThirdParty/libdbusmenu-qt + url = https://github.com/desktop-app/libdbusmenu-qt.git diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ece9768a0..c9fc8b20b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -77,6 +77,14 @@ if (DESKTOP_APP_USE_PACKAGED) ) endif() +if (LINUX AND NOT TDESKTOP_DISABLE_DBUS_INTEGRATION) + target_link_libraries(Telegram + PRIVATE + desktop-app::external_statusnotifieritem + desktop-app::external_dbusmenu_qt + ) +endif() + target_link_libraries(Telegram PRIVATE tdesktop::lib_mtproto @@ -1059,28 +1067,6 @@ elseif (LINUX) find_library(X11_LIBRARY X11) target_link_libraries(Telegram PRIVATE ${X11_LIBRARY}) endif() - - set(appindicator_packages - ayatana-appindicator3-0.1 - ayatana-appindicator-0.1 - appindicator3-0.1 - appindicator-0.1 - ) - set(appindicator_found 0) - foreach (package ${appindicator_packages}) - pkg_check_modules(APPIND_${package} ${package}) - if (APPIND_${package}_FOUND) - set(appindicator_found 1) - target_include_directories(Telegram PRIVATE "${APPIND_${package}_INCLUDE_DIRS}") - if (${package} MATCHES "ayatana") - target_compile_definitions(Telegram PRIVATE TDESKTOP_USE_AYATANA_INDICATORS) - endif() - break() - endif() - endforeach() - if (NOT ${appindicator_found}) - message(FATAL_ERROR "No libappindicator found by pkg-config.") - endif() endif() endif() diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index ad106e581..3388cb186 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -144,14 +144,10 @@ void MainWindow::firstShow() { if (Platform::IsLinux()) { trayIconMenu->addAction(tr::lng_open_from_tray(tr::now), this, SLOT(showFromTray())); - trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), this, SLOT(minimizeToTray())); - trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray())); - trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), this, SLOT(quitFromTray())); - } else { - trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), this, SLOT(minimizeToTray())); - trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray())); - trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), this, SLOT(quitFromTray())); } + trayIconMenu->addAction(tr::lng_minimize_to_tray(tr::now), this, SLOT(minimizeToTray())); + trayIconMenu->addAction(notificationActionText, this, SLOT(toggleDisplayNotifyFromTray())); + trayIconMenu->addAction(tr::lng_quit_from_tray(tr::now), this, SLOT(quitFromTray())); Global::RefWorkMode().setForced(Global::WorkMode().value(), true); psFirstShow(); @@ -564,7 +560,7 @@ void MainWindow::updateTrayMenu(bool force) { auto actions = iconMenu->actions(); if (Platform::IsLinux()) { auto minimizeAction = actions.at(1); - minimizeAction->setDisabled(!isVisible()); + minimizeAction->setEnabled(isVisible()); } else { updateIsActive(0); auto active = isActive(); @@ -588,12 +584,6 @@ void MainWindow::updateTrayMenu(bool force) { : tr::lng_enable_notifications_from_tray(tr::now); notificationAction->setText(notificationActionText); -#ifndef Q_OS_WIN - if (trayIcon && trayIcon->contextMenu() != iconMenu) { - trayIcon->setContextMenu(iconMenu); - } -#endif // !Q_OS_WIN - psTrayMenuUpdated(); } diff --git a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp index 5fff698f3..aa5c6c0d8 100644 --- a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp @@ -9,10 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/specific_linux.h" -#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -#include -#endif - namespace Platform { namespace DesktopEnvironment { namespace { @@ -44,18 +40,15 @@ Type Compute() { return Type::Gnome; } return Type::Unity; - } else if (list.contains("pantheon")) { - return Type::Pantheon; } else if (list.contains("gnome")) { - if (list.contains("ubuntu")) - return Type::Ubuntu; - return Type::Gnome; } else if (list.contains("kde")) { if (kdeSession == qstr("5")) { return Type::KDE5; } return Type::KDE4; + } else if (list.contains("mate")) { + return Type::MATE; } } @@ -70,6 +63,8 @@ Type Compute() { return Type::KDE4; } return Type::KDE3; + } else if (desktopSession == qstr("mate")) { + return Type::MATE; } } @@ -96,9 +91,8 @@ Type ComputeAndLog() { case Type::KDE3: return "KDE3"; case Type::KDE4: return "KDE4"; case Type::KDE5: return "KDE5"; - case Type::Ubuntu: return "Ubuntu"; case Type::Unity: return "Unity"; - case Type::Pantheon: return "Pantheon"; + case Type::MATE: return "MATE"; } return QString::number(static_cast(result)); }; @@ -114,20 +108,5 @@ Type Get() { return result; } -bool TryQtTrayIcon() { - return !IsPantheon(); -} - -bool PreferAppIndicatorTrayIcon() { - return (InSandbox() && !IsKDE()) - || IsUnity() - || IsUbuntu() -#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - || (IsGnome() && QDBusInterface("org.kde.StatusNotifierWatcher", "/").isValid()); -#else - || IsGnome(); -#endif -} - } // namespace DesktopEnvironment } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h index 129b9ef9f..2a2f082c2 100644 --- a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h +++ b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h @@ -16,9 +16,8 @@ enum class Type { KDE3, KDE4, KDE5, - Ubuntu, Unity, - Pantheon, + MATE, }; Type Get(); @@ -43,20 +42,13 @@ inline bool IsKDE() { return IsKDE3() || IsKDE4() || IsKDE5(); } -inline bool IsUbuntu() { - return Get() == Type::Ubuntu; -} - inline bool IsUnity() { return Get() == Type::Unity; } -inline bool IsPantheon() { - return Get() == Type::Pantheon; +inline bool IsMATE() { + return Get() == Type::MATE; } -bool TryQtTrayIcon(); -bool PreferAppIndicatorTrayIcon(); - } // namespace DesktopEnvironment } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.cpp b/Telegram/SourceFiles/platform/linux/linux_libs.cpp index fd628a20d..1bfe08d22 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_libs.cpp @@ -126,16 +126,6 @@ bool setupGtkBase(QLibrary &lib_gtk) { DEBUG_LOG(("Checked gtk with gtk_init_check!")); return true; } - -bool setupAppIndicator(QLibrary &lib_indicator) { - if (!load(lib_indicator, "app_indicator_new", app_indicator_new)) return false; - if (!load(lib_indicator, "app_indicator_set_status", app_indicator_set_status)) return false; - if (!load(lib_indicator, "app_indicator_set_menu", app_indicator_set_menu)) return false; - if (!load(lib_indicator, "app_indicator_set_icon_full", app_indicator_set_icon_full)) return false; - - DEBUG_LOG(("Library appindicator functions loaded!")); - return true; -} #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } // namespace @@ -197,10 +187,6 @@ f_g_type_check_instance_cast g_type_check_instance_cast = nullptr; f_g_type_check_instance_is_a g_type_check_instance_is_a = nullptr; f_g_signal_connect_data g_signal_connect_data = nullptr; f_g_signal_handler_disconnect g_signal_handler_disconnect = nullptr; -f_app_indicator_new app_indicator_new = nullptr; -f_app_indicator_set_status app_indicator_set_status = nullptr; -f_app_indicator_set_menu app_indicator_set_menu = nullptr; -f_app_indicator_set_icon_full app_indicator_set_icon_full = nullptr; f_gdk_init_check gdk_init_check = nullptr; f_gdk_pixbuf_new_from_data gdk_pixbuf_new_from_data = nullptr; f_gdk_pixbuf_new_from_file gdk_pixbuf_new_from_file = nullptr; @@ -233,33 +219,14 @@ void start() { #ifndef TDESKTOP_DISABLE_GTK_INTEGRATION bool gtkLoaded = false; - bool indicatorLoaded = false; bool isWayland = QGuiApplication::platformName().startsWith(qsl("wayland"), Qt::CaseInsensitive); - QLibrary lib_gtk, lib_indicator; - if (loadLibrary(lib_indicator, "ayatana-appindicator3", 1) || loadLibrary(lib_indicator, "appindicator3", 1)) { - if (loadLibrary(lib_gtk, "gtk-3", 0)) { - gtkLoaded = setupGtkBase(lib_gtk); - indicatorLoaded = setupAppIndicator(lib_indicator); - } - } - if ((!gtkLoaded || !indicatorLoaded) && !isWayland) { - if (loadLibrary(lib_indicator, "ayatana-appindicator", 1) || loadLibrary(lib_indicator, "appindicator", 1)) { - if (loadLibrary(lib_gtk, "gtk-x11-2.0", 0)) { - gtkLoaded = indicatorLoaded = false; - gtkLoaded = setupGtkBase(lib_gtk); - indicatorLoaded = setupAppIndicator(lib_indicator); - } - } - } + QLibrary lib_gtk; - // If no appindicator, try at least load gtk. - if (!gtkLoaded && !indicatorLoaded) { - if (loadLibrary(lib_gtk, "gtk-3", 0)) { - gtkLoaded = setupGtkBase(lib_gtk); - } - if (!gtkLoaded && !isWayland && loadLibrary(lib_gtk, "gtk-x11-2.0", 0)) { - gtkLoaded = setupGtkBase(lib_gtk); - } + if (loadLibrary(lib_gtk, "gtk-3", 0)) { + gtkLoaded = setupGtkBase(lib_gtk); + } + if (!gtkLoaded && !isWayland && loadLibrary(lib_gtk, "gtk-x11-2.0", 0)) { + gtkLoaded = setupGtkBase(lib_gtk); } if (gtkLoaded) { @@ -287,7 +254,7 @@ void start() { load(lib_gtk, "gtk_button_set_label", gtk_button_set_label); load(lib_gtk, "gtk_button_get_type", gtk_button_get_type); } else { - LOG(("Could not load gtk-x11-2.0!")); + LOG(("Could not load gtk-3 or gtk-x11-2.0!")); } #endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h index 769bea696..bb6b51bb7 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ b/Telegram/SourceFiles/platform/linux/linux_libs.h @@ -13,11 +13,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL extern "C" { #undef signals -#ifdef TDESKTOP_USE_AYATANA_INDICATORS -#include -#else -#include -#endif #include #include #define signals public @@ -278,18 +273,6 @@ inline gulong g_signal_connect_swapped_helper(gpointer instance, const gchar *de typedef void (*f_g_signal_handler_disconnect)(gpointer instance, gulong handler_id); extern f_g_signal_handler_disconnect g_signal_handler_disconnect; -typedef AppIndicator* (*f_app_indicator_new)(const gchar *id, const gchar *icon_name, AppIndicatorCategory category); -extern f_app_indicator_new app_indicator_new; - -typedef void (*f_app_indicator_set_status)(AppIndicator *self, AppIndicatorStatus status); -extern f_app_indicator_set_status app_indicator_set_status; - -typedef void (*f_app_indicator_set_menu)(AppIndicator *self, GtkMenu *menu); -extern f_app_indicator_set_menu app_indicator_set_menu; - -typedef void (*f_app_indicator_set_icon_full)(AppIndicator *self, const gchar *icon_name, const gchar *icon_desc); -extern f_app_indicator_set_icon_full app_indicator_set_icon_full; - typedef gboolean (*f_gdk_init_check)(gint *argc, gchar ***argv); extern f_gdk_init_check gdk_init_check; diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index ec2b4db41..83be08f3e 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "mainwindow.h" #include "core/application.h" +#include "core/sandbox.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "facades.h" @@ -22,101 +23,77 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION #include -#endif - -#include -#include +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION namespace Platform { namespace { -bool noQtTrayIcon = false, tryAppIndicator = false; -bool useGtkBase = false, useAppIndicator = false, useStatusIcon = false, trayIconChecked = false, useUnityCount = false; - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -AppIndicator *_trayIndicator = 0; -GtkStatusIcon *_trayIcon = 0; -GtkWidget *_trayMenu = 0; -GdkPixbuf *_trayPixbuf = 0; -QByteArray _trayPixbufData; -QList > _trayItems; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +constexpr auto kDisableTrayCounter = "TDESKTOP_DISABLE_TRAY_COUNTER"_cs; +constexpr auto kTrayIconName = "telegram"_cs; +constexpr auto kPanelTrayIconName = "telegram-panel"_cs; +constexpr auto kMutePanelTrayIconName = "telegram-mute-panel"_cs; +constexpr auto kAttentionPanelTrayIconName = "telegram-attention-panel"_cs; +constexpr auto kSNIWatcherService = "org.kde.StatusNotifierWatcher"_cs; +constexpr auto kTrayIconFilename = "tdesktop-trayicon-XXXXXX.png"_cs; int32 _trayIconSize = 48; bool _trayIconMuted = true; int32 _trayIconCount = 0; QImage _trayIconImageBack, _trayIconImage; QString _trayIconThemeName, _trayIconName; -QString _desktopFile; #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION -QString _dbusPath = "/"; -#endif - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -void _trayIconPopup(GtkStatusIcon *status_icon, guint button, guint32 activate_time, gpointer popup_menu) { - Libs::gtk_menu_popup(Libs::gtk_menu_cast(popup_menu), NULL, NULL, Libs::gtk_status_icon_position_menu, status_icon, button, activate_time); -} - -void _trayIconActivate(GtkStatusIcon *status_icon, gpointer popup_menu) { - if (App::wnd()->isActiveWindow() && App::wnd()->isVisible()) { - Libs::gtk_menu_popup(Libs::gtk_menu_cast(popup_menu), NULL, NULL, Libs::gtk_status_icon_position_menu, status_icon, 0, Libs::gtk_get_current_event_time()); - } else { - App::wnd()->showFromTray(); - } -} - -gboolean _trayIconResized(GtkStatusIcon *status_icon, gint size, gpointer popup_menu) { - _trayIconSize = size; - if (Global::started()) Notify::unreadCounterUpdated(); - return FALSE; -} -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +bool UseUnityCount = false; +QString UnityCountDesktopFile; +QString UnityCountDBusPath = "/"; +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION #define QT_RED 0 #define QT_GREEN 1 #define QT_BLUE 2 #define QT_ALPHA 3 -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION -#define GTK_RED 2 -#define GTK_GREEN 1 -#define GTK_BLUE 0 -#define GTK_ALPHA 3 -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +QString GetTrayIconName() { + const auto counter = Core::App().unreadBadge(); + const auto muted = Core::App().unreadBadgeMuted(); -QImage _trayIconImageGen(bool useSystemIcon) { + return (counter > 0) + ? (muted + ? kMutePanelTrayIconName.utf16() + : kAttentionPanelTrayIconName.utf16()) + : kPanelTrayIconName.utf16(); +} + +QImage TrayIconImageGen() { const auto counter = Core::App().unreadBadge(); const auto muted = Core::App().unreadBadgeMuted(); const auto counterSlice = (counter >= 1000) ? (1000 + (counter % 100)) : counter; - QString iconThemeName = QIcon::themeName(); - QString iconName = (counter > 0) - ? (muted ? "telegram-mute-panel" : "telegram-attention-panel") - : "telegram-panel"; + const auto iconThemeName = QIcon::themeName(); + const auto iconName = GetTrayIconName(); - if (_trayIconImage.isNull() || _trayIconImage.width() != _trayIconSize - || (useSystemIcon && (iconThemeName != _trayIconThemeName - || iconName != _trayIconName)) - || muted != _trayIconMuted || counterSlice != _trayIconCount) { + if (_trayIconImage.isNull() + || _trayIconImage.width() != _trayIconSize + || iconThemeName != _trayIconThemeName + || iconName != _trayIconName + || muted != _trayIconMuted + || counterSlice != _trayIconCount) { if (_trayIconImageBack.isNull() || _trayIconImageBack.width() != _trayIconSize || iconThemeName != _trayIconThemeName || iconName != _trayIconName) { _trayIconImageBack = Core::App().logo(); - if (useSystemIcon) { - _trayIconImageBack = QIcon::fromTheme( - iconName, - QIcon::fromTheme( - "telegram", - QIcon(QPixmap::fromImage(_trayIconImageBack))) - ).pixmap(_trayIconSize, _trayIconSize).toImage(); - } + _trayIconImageBack = QIcon::fromTheme( + iconName, + QIcon::fromTheme( + kTrayIconName.utf16(), + QIcon(QPixmap::fromImage(_trayIconImageBack))) + ).pixmap(_trayIconSize, _trayIconSize).toImage(); - int w = _trayIconImageBack.width(), + auto w = _trayIconImageBack.width(), h = _trayIconImageBack.height(); if (w != _trayIconSize || h != _trayIconSize) { @@ -132,8 +109,8 @@ QImage _trayIconImageGen(bool useSystemIcon) { w = _trayIconImageBack.width(); h = _trayIconImageBack.height(); - int perline = _trayIconImageBack.bytesPerLine(); - uchar *bytes = _trayIconImageBack.bits(); + const auto perline = _trayIconImageBack.bytesPerLine(); + auto *bytes = _trayIconImageBack.bits(); for (int32 y = 0; y < h; ++y) { for (int32 x = 0; x < w; ++x) { @@ -189,81 +166,66 @@ QImage _trayIconImageGen(bool useSystemIcon) { return _trayIconImage; } -QString _trayIconImageFile() { - const auto counter = Core::App().unreadBadge(); - const auto muted = Core::App().unreadBadgeMuted(); - const auto counterSlice = (counter >= 1000) - ? (1000 + (counter % 100)) - : counter; - - QString iconThemeName = QIcon::themeName(); - - QString name = cWorkingDir() + qsl("tdata/ticons/icon%1_%2_%3_%4.png") - .arg(muted ? "mute" : "") - .arg(iconThemeName) - .arg(_trayIconSize) - .arg(counterSlice); - - QFileInfo info(name); - if (info.exists()) return name; - - QImage img = _trayIconImageGen(false); - if (img.save(name, "PNG")) return name; - - QDir dir(info.absoluteDir()); - if (!dir.exists()) { - dir.mkpath(dir.absolutePath()); - if (img.save(name, "PNG")) return name; - } - - return QString(); -} -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - -void loadPixbuf(QImage image) { - int w = image.width(), h = image.height(), perline = image.bytesPerLine(), s = image.byteCount(); - _trayPixbufData.resize(w * h * 4); - uchar *result = (uchar*)_trayPixbufData.data(), *bytes = image.bits(); - for (int32 y = 0; y < h; ++y) { - for (int32 x = 0; x < w; ++x) { - int32 offset = (y * w + x) * 4, srcoff = y * perline + x * 4; - result[offset + GTK_RED ] = bytes[srcoff + QT_RED ]; - result[offset + GTK_GREEN] = bytes[srcoff + QT_GREEN]; - result[offset + GTK_BLUE ] = bytes[srcoff + QT_BLUE ]; - result[offset + GTK_ALPHA] = bytes[srcoff + QT_ALPHA]; +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION +static bool NeedTrayIconFile() { + // 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 TrayIconFileNeeded = [&] { + auto necessary = false; + const auto session = QDBusConnection::sessionBus(); + const auto pid = session.interface() + ->servicePid(kSNIWatcherService.utf16()).value(); + const auto processName = ProcessNameByPID(QString::number(pid)); + necessary = processName.endsWith( + qsl("indicator-application-service")); + if (!necessary) { + // Accessing to process name might be not allowed if the application + // is confined, thus we can just rely on the current desktop in use + necessary = DesktopEnvironment::IsUnity() + || DesktopEnvironment::IsMATE(); } - } - - if (_trayPixbuf) Libs::g_object_unref(_trayPixbuf); - _trayPixbuf = Libs::gdk_pixbuf_new_from_data(result, GDK_COLORSPACE_RGB, true, 8, w, h, w * 4, 0, 0); + return necessary; + }(); + return TrayIconFileNeeded; } -void _trayMenuCallback(GtkMenu *menu, gpointer data) { - for (int32 i = 0, l = _trayItems.size(); i < l; ++i) { - if ((void*)_trayItems.at(i).first == (void*)menu) { - QMetaObject::invokeMethod(_trayItems.at(i).second, "triggered"); - } - } +static inline QString TrayIconFileTemplate() { + static const auto TempFileTemplate = AppRuntimeDirectory() + + kTrayIconFilename.utf16(); + return TempFileTemplate; } -static gboolean _trayIconCheck(gpointer/* pIn*/) { - if (useStatusIcon && !trayIconChecked) { - if (Libs::gtk_status_icon_is_embedded(_trayIcon)) { - trayIconChecked = true; - cSetSupportTray(true); - if (Global::started()) { - Global::RefWorkMode().setForced(Global::WorkMode().value(), true); - } - if (App::wnd()) { - Notify::unreadCounterUpdated(); - App::wnd()->updateTrayMenu(); - } - } - } - return FALSE; +std::unique_ptr TrayIconFile( + const QPixmap &icon, QObject *parent) { + auto ret = std::make_unique( + TrayIconFileTemplate(), + parent); + ret->open(); + icon.save(ret.get()); + ret->close(); + return ret; } +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +bool IsSNIAvailable() { + static const auto SNIAvailable = [&] { +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + QDBusInterface systrayHost( + kSNIWatcherService.utf16(), + qsl("/StatusNotifierWatcher"), + kSNIWatcherService.utf16()); + + return systrayHost.isValid() + && systrayHost + .property("IsStatusNotifierHostRegistered") + .toBool(); +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + return false; + }(); + + return SNIAvailable; +} quint32 djbStringHash(QString string) { quint32 hash = 5381; @@ -278,71 +240,106 @@ quint32 djbStringHash(QString string) { MainWindow::MainWindow(not_null controller) : Window::MainWindow(controller) { - connect(&_psCheckStatusIconTimer, SIGNAL(timeout()), this, SLOT(psStatusIconCheck())); - _psCheckStatusIconTimer.setSingleShot(false); - - connect(&_psUpdateIndicatorTimer, SIGNAL(timeout()), this, SLOT(psUpdateIndicator())); - _psUpdateIndicatorTimer.setSingleShot(true); } bool MainWindow::hasTrayIcon() const { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - return trayIcon || ((useAppIndicator || (useStatusIcon && trayIconChecked)) && (Global::WorkMode().value() != dbiwmWindowOnly)); +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + return trayIcon || _sniTrayIcon; #else return trayIcon; -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION -} - -void MainWindow::psStatusIconCheck() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - _trayIconCheck(0); -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - if (cSupportTray() || !--_psCheckStatusIconLeft) { - _psCheckStatusIconTimer.stop(); - return; - } +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } void MainWindow::psShowTrayMenu() { + if (!IsSNIAvailable()) { + _trayIconMenuXEmbed->popup(QCursor::pos()); + } } void MainWindow::psTrayMenuUpdated() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (noQtTrayIcon && (useAppIndicator || useStatusIcon)) { - const QList &actions = trayIconMenu->actions(); - if (_trayItems.isEmpty()) { - DEBUG_LOG(("Creating tray menu!")); - for (int32 i = 0, l = actions.size(); i != l; ++i) { - GtkWidget *item = Libs::gtk_menu_item_new_with_label(actions.at(i)->text().toUtf8()); - Libs::gtk_menu_shell_append(Libs::gtk_menu_shell_cast(_trayMenu), item); - Libs::g_signal_connect_helper(item, "activate", G_CALLBACK(_trayMenuCallback), this); - Libs::gtk_widget_show(item); - Libs::gtk_widget_set_sensitive(item, actions.at(i)->isEnabled()); - - _trayItems.push_back(qMakePair(item, actions.at(i))); - } - } else { - DEBUG_LOG(("Updating tray menu!")); - for (int32 i = 0, l = actions.size(); i != l; ++i) { - if (i < _trayItems.size()) { - Libs::gtk_menu_item_set_label(reinterpret_cast(_trayItems.at(i).first), actions.at(i)->text().toUtf8()); - Libs::gtk_widget_set_sensitive(_trayItems.at(i).first, actions.at(i)->isEnabled()); - } - } +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + if (IsSNIAvailable()) { + if (_sniTrayIcon && trayIconMenu) { + _sniTrayIcon->setContextMenu(trayIconMenu); } } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION +void MainWindow::setSNITrayIcon( + const QIcon &icon, const QPixmap &iconPixmap) { + if (!NeedTrayIconFile()) { + _sniTrayIcon->setIconByPixmap(icon); + _sniTrayIcon->setToolTipIconByPixmap(icon); + } + + if (qEnvironmentVariableIsSet(kDisableTrayCounter.utf8())) { + const auto iconName = GetTrayIconName(); + _sniTrayIcon->setIconByName(iconName); + _sniTrayIcon->setToolTipIconByName(iconName); + } else if (NeedTrayIconFile()) { + _trayIconFile = TrayIconFile(iconPixmap, this); + + if (_trayIconFile) { + _sniTrayIcon->setIconByName(_trayIconFile->fileName()); + _sniTrayIcon->setToolTipIconByName(_trayIconFile->fileName()); + } + } +} + +void MainWindow::attachToSNITrayIcon() { + _sniTrayIcon->setToolTipTitle(AppName.utf16()); + connect(_sniTrayIcon, + &StatusNotifierItem::activateRequested, + this, + [=](const QPoint &) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + handleTrayIconActication(QSystemTrayIcon::Trigger); + }); + }); + connect(_sniTrayIcon, + &StatusNotifierItem::secondaryActivateRequested, + this, + [=](const QPoint &) { + Core::Sandbox::Instance().customEnterFromEventLoop([&] { + handleTrayIconActication(QSystemTrayIcon::MiddleClick); + }); + }); + updateTrayMenu(); +} +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + void MainWindow::psSetupTrayIcon() { - if (noQtTrayIcon) { - if (!cSupportTray()) return; + const auto iconPixmap = QPixmap::fromImage(TrayIconImageGen()); + const auto icon = QIcon(iconPixmap); + + if (IsSNIAvailable()) { +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + LOG(("Using SNI tray icon.")); + if (!_sniTrayIcon) { + _sniTrayIcon = new StatusNotifierItem( + QCoreApplication::applicationName(), + this); + + _sniTrayIcon->setTitle(QCoreApplication::applicationName()); + setSNITrayIcon(icon, iconPixmap); + + attachToSNITrayIcon(); + } updateIconCounters(); +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } else { LOG(("Using Qt tray icon.")); + + if (!_trayIconMenuXEmbed) { + _trayIconMenuXEmbed = new Ui::PopupMenu(nullptr, trayIconMenu); + _trayIconMenuXEmbed->deleteOnHide(false); + } + if (!trayIcon) { trayIcon = new QSystemTrayIcon(this); - trayIcon->setIcon(QIcon(QPixmap::fromImage(_trayIconImageGen(true)))); + trayIcon->setIcon(icon); attachToTrayIcon(trayIcon); } @@ -356,14 +353,14 @@ void MainWindow::workmodeUpdated(DBIWorkMode mode) { if (!cSupportTray()) return; if (mode == dbiwmWindowOnly) { - if (noQtTrayIcon) { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (useAppIndicator) { - Libs::app_indicator_set_status(_trayIndicator, APP_INDICATOR_STATUS_PASSIVE); - } else if (useStatusIcon) { - Libs::gtk_status_icon_set_visible(_trayIcon, false); + if (IsSNIAvailable()) { +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + if (_sniTrayIcon) { + _sniTrayIcon->setContextMenu(0); + _sniTrayIcon->deleteLater(); } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION + _sniTrayIcon = 0; +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } else { if (trayIcon) { trayIcon->setContextMenu(0); @@ -372,35 +369,10 @@ void MainWindow::workmodeUpdated(DBIWorkMode mode) { trayIcon = 0; } } else { - if (noQtTrayIcon) { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (useAppIndicator) { - Libs::app_indicator_set_status(_trayIndicator, APP_INDICATOR_STATUS_ACTIVE); - } else if (useStatusIcon) { - Libs::gtk_status_icon_set_visible(_trayIcon, true); - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION - } else { - psSetupTrayIcon(); - } + psSetupTrayIcon(); } } -void MainWindow::psUpdateIndicator() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - _psUpdateIndicatorTimer.stop(); - _psLastIndicatorUpdate = crl::now(); - QFileInfo iconFile(_trayIconImageFile()); - if (iconFile.exists()) { - QByteArray path = QFile::encodeName(iconFile.absoluteFilePath()), name = QFile::encodeName(iconFile.fileName()); - name = name.mid(0, name.size() - 4); - Libs::app_indicator_set_icon_full(_trayIndicator, path.constData(), name); - } else { - useAppIndicator = false; - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION -} - void MainWindow::unreadCounterChangedHook() { setWindowTitle(titleText()); updateIconCounters(); @@ -410,208 +382,91 @@ void MainWindow::updateIconCounters() { updateWindowIcon(); #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - if (useUnityCount) { + if (UseUnityCount) { const auto counter = Core::App().unreadBadge(); QVariantMap dbusUnityProperties; if (counter > 0) { // Gnome requires that count is a 64bit integer - dbusUnityProperties.insert("count", (qint64) ((counter > 9999) ? 9999 : (counter))); + dbusUnityProperties.insert( + "count", + (qint64) ((counter > 9999) + ? 9999 + : (counter))); dbusUnityProperties.insert("count-visible", true); } else { dbusUnityProperties.insert("count-visible", false); } - QDBusMessage signal = QDBusMessage::createSignal(_dbusPath, "com.canonical.Unity.LauncherEntry", "Update"); - signal << "application://" + _desktopFile; + QDBusMessage signal = QDBusMessage::createSignal( + UnityCountDBusPath, + "com.canonical.Unity.LauncherEntry", + "Update"); + signal << "application://" + UnityCountDesktopFile; signal << dbusUnityProperties; QDBusConnection::sessionBus().send(signal); } -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - if (noQtTrayIcon) { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (useAppIndicator) { - if (crl::now() > _psLastIndicatorUpdate + 1000) { - psUpdateIndicator(); - } else if (!_psUpdateIndicatorTimer.isActive()) { - _psUpdateIndicatorTimer.start(100); - } - } else if (useStatusIcon && trayIconChecked) { - loadPixbuf(_trayIconImageGen(true)); - Libs::gtk_status_icon_set_from_pixbuf(_trayIcon, _trayPixbuf); + const auto iconPixmap = QPixmap::fromImage(TrayIconImageGen()); + const auto icon = QIcon(iconPixmap); + + if (IsSNIAvailable()) { +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + if (_sniTrayIcon) { + setSNITrayIcon(icon, iconPixmap); } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION } else if (trayIcon) { - trayIcon->setIcon(QIcon(QPixmap::fromImage(_trayIconImageGen(true)))); + trayIcon->setIcon(icon); } } void MainWindow::LibsLoaded() { - noQtTrayIcon = !DesktopEnvironment::TryQtTrayIcon(); -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - tryAppIndicator = DesktopEnvironment::PreferAppIndicatorTrayIcon(); -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION - LOG(("Tray Icon: Try Qt = %1, Prefer appindicator = %2").arg(Logs::b(!noQtTrayIcon)).arg(Logs::b(tryAppIndicator))); - - if (noQtTrayIcon) cSetSupportTray(false); - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - useGtkBase = (Libs::gtk_init_check != nullptr) - && (Libs::gtk_menu_new != nullptr) - && (Libs::gtk_menu_get_type != nullptr) - && (Libs::gtk_menu_item_new_with_label != nullptr) - && (Libs::gtk_menu_item_set_label != nullptr) - && (Libs::gtk_menu_shell_append != nullptr) - && (Libs::gtk_menu_shell_get_type != nullptr) - && (Libs::gtk_widget_show != nullptr) - && (Libs::gtk_widget_get_toplevel != nullptr) - && (Libs::gtk_widget_get_visible != nullptr) - && (Libs::gtk_widget_set_sensitive != nullptr) - && (Libs::g_type_check_instance_cast != nullptr) - && (Libs::g_signal_connect_data != nullptr) - && (Libs::g_object_ref_sink != nullptr) - && (Libs::g_object_unref != nullptr); - - useAppIndicator = useGtkBase - && (Libs::app_indicator_new != nullptr) - && (Libs::app_indicator_set_status != nullptr) - && (Libs::app_indicator_set_menu != nullptr) - && (Libs::app_indicator_set_icon_full != nullptr); - - if (tryAppIndicator && useGtkBase && useAppIndicator) { - noQtTrayIcon = true; - cSetSupportTray(false); + if (!IsSNIAvailable()) { + _trayIconSize = 22; } - - useStatusIcon = (Libs::gdk_init_check != nullptr) - && (Libs::gdk_pixbuf_new_from_data != nullptr) - && (Libs::gtk_status_icon_new_from_pixbuf != nullptr) - && (Libs::gtk_status_icon_set_from_pixbuf != nullptr) - && (Libs::gtk_status_icon_new_from_file != nullptr) - && (Libs::gtk_status_icon_set_from_file != nullptr) - && (Libs::gtk_status_icon_set_title != nullptr) - && (Libs::gtk_status_icon_set_tooltip_text != nullptr) - && (Libs::gtk_status_icon_set_visible != nullptr) - && (Libs::gtk_status_icon_is_embedded != nullptr) - && (Libs::gtk_status_icon_get_geometry != nullptr) - && (Libs::gtk_status_icon_position_menu != nullptr) - && (Libs::gtk_menu_popup != nullptr) - && (Libs::gtk_get_current_event_time != nullptr) - && (Libs::g_idle_add != nullptr); - if (useStatusIcon) { - DEBUG_LOG(("Status icon api loaded!")); - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION -} - -void MainWindow::psCreateTrayIcon() { - if (!noQtTrayIcon) { - LOG(("Tray Icon: Using Qt tray icon, available: %1").arg(Logs::b(QSystemTrayIcon::isSystemTrayAvailable()))); - cSetSupportTray(QSystemTrayIcon::isSystemTrayAvailable()); - return; - } - -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (useAppIndicator) { - DEBUG_LOG(("Trying to create AppIndicator")); - _trayMenu = Libs::gtk_menu_new(); - if (_trayMenu) { - DEBUG_LOG(("Created gtk menu for appindicator!")); - QFileInfo iconFile(_trayIconImageFile()); - if (iconFile.exists()) { - QByteArray path = QFile::encodeName(iconFile.absoluteFilePath()); - _trayIndicator = Libs::app_indicator_new("Telegram Desktop", path.constData(), APP_INDICATOR_CATEGORY_APPLICATION_STATUS); - if (_trayIndicator) { - LOG(("Tray Icon: Using appindicator tray icon.")); - } else { - DEBUG_LOG(("Failed to app_indicator_new()!")); - } - } else { - useAppIndicator = false; - DEBUG_LOG(("Failed to create image file!")); - } - } else { - DEBUG_LOG(("Failed to gtk_menu_new()!")); - } - if (_trayMenu && _trayIndicator) { - Libs::app_indicator_set_status(_trayIndicator, APP_INDICATOR_STATUS_ACTIVE); - Libs::app_indicator_set_menu(_trayIndicator, Libs::gtk_menu_cast(_trayMenu)); - useStatusIcon = false; - } else { - DEBUG_LOG(("AppIndicator failed!")); - useAppIndicator = false; - } - } - if (useStatusIcon) { - if (Libs::gdk_init_check(0, 0)) { - if (!_trayMenu) _trayMenu = Libs::gtk_menu_new(); - if (_trayMenu) { - loadPixbuf(_trayIconImageGen(true)); - _trayIcon = Libs::gtk_status_icon_new_from_pixbuf(_trayPixbuf); - if (_trayIcon) { - LOG(("Tray Icon: Using GTK status tray icon.")); - - Libs::g_signal_connect_helper(_trayIcon, "popup-menu", GCallback(_trayIconPopup), _trayMenu); - Libs::g_signal_connect_helper(_trayIcon, "activate", GCallback(_trayIconActivate), _trayMenu); - Libs::g_signal_connect_helper(_trayIcon, "size-changed", GCallback(_trayIconResized), _trayMenu); - - Libs::gtk_status_icon_set_title(_trayIcon, "Telegram Desktop"); - Libs::gtk_status_icon_set_tooltip_text(_trayIcon, "Telegram Desktop"); - Libs::gtk_status_icon_set_visible(_trayIcon, true); - } else { - useStatusIcon = false; - } - } else { - useStatusIcon = false; - } - } else { - useStatusIcon = false; - } - } - if (!useStatusIcon && !useAppIndicator) { - LOG(("Tray Icon: Not able to use any tray icon :(")); - if (_trayMenu) { - Libs::g_object_ref_sink(_trayMenu); - Libs::g_object_unref(_trayMenu); - _trayMenu = nullptr; - } - } - cSetSupportTray(useAppIndicator); - if (useStatusIcon) { - Libs::g_idle_add((GSourceFunc)_trayIconCheck, 0); - _psCheckStatusIconTimer.start(100); - } else { - workmodeUpdated(Global::WorkMode().value()); - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION } void MainWindow::psFirstShow() { - psCreateTrayIcon(); + const auto trayAvailable = IsSNIAvailable() + || QSystemTrayIcon::isSystemTrayAvailable(); + + LOG(("System tray available: %1").arg(Logs::b(trayAvailable))); + cSetSupportTray(trayAvailable); #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION if (QDBusInterface("com.canonical.Unity", "/").isValid()) { - std::vector possibleDesktopFiles = { + const std::vector possibleDesktopFiles = { GetLauncherFilename(), "Telegram.desktop" }; - for (auto it = possibleDesktopFiles.begin(); it != possibleDesktopFiles.end(); it++) { - if (!QStandardPaths::locate(QStandardPaths::ApplicationsLocation, *it).isEmpty()) { - _desktopFile = *it; - LOG(("Found Unity Launcher entry %1!").arg(_desktopFile)); - useUnityCount = true; + for (auto it = possibleDesktopFiles.begin(); + it != possibleDesktopFiles.end(); it++) { + if (!QStandardPaths::locate( + QStandardPaths::ApplicationsLocation, *it).isEmpty()) { + UnityCountDesktopFile = *it; + LOG(("Found Unity Launcher entry %1!") + .arg(UnityCountDesktopFile)); + UseUnityCount = true; break; } } - if (!useUnityCount) { + if (!UseUnityCount) { LOG(("Could not get Unity Launcher entry!")); } - _dbusPath = "/com/canonical/unity/launcherentry/" + QString::number(djbStringHash("application://" + _desktopFile)); + UnityCountDBusPath = "/com/canonical/unity/launcherentry/" + + QString::number( + djbStringHash("application://" + UnityCountDesktopFile)); } else { LOG(("Not using Unity Launcher count.")); } -#endif +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION show(); if (cWindowPos().maximized) { @@ -619,13 +474,15 @@ void MainWindow::psFirstShow() { setWindowState(Qt::WindowMaximized); } - if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized()) || cStartInTray()) { + if ((cLaunchMode() == LaunchModeAutoStart && cStartMinimized()) + || cStartInTray()) { // If I call hide() synchronously here after show() then on Ubuntu 14.04 // it will show a window frame with transparent window body, without content. // And to be able to "Show from tray" one more hide() will be required. crl::on_main(this, [=] { setWindowState(Qt::WindowMinimized); - if (Global::WorkMode().value() == dbiwmTrayOnly || Global::WorkMode().value() == dbiwmWindowAndTray) { + if (Global::WorkMode().value() == dbiwmTrayOnly + || Global::WorkMode().value() == dbiwmWindowAndTray) { hide(); } else { show(); @@ -637,25 +494,11 @@ void MainWindow::psFirstShow() { } MainWindow::~MainWindow() { -#ifndef TDESKTOP_DISABLE_GTK_INTEGRATION - if (_trayIcon) { - Libs::g_object_unref(_trayIcon); - _trayIcon = nullptr; - } - if (_trayPixbuf) { - Libs::g_object_unref(_trayPixbuf); - _trayPixbuf = nullptr; - } - if (_trayMenu) { - Libs::g_object_ref_sink(_trayMenu); - Libs::g_object_unref(_trayMenu); - _trayMenu = nullptr; - } - if (_trayIndicator) { - Libs::g_object_unref(_trayIndicator); - _trayIndicator = nullptr; - } -#endif // !TDESKTOP_DISABLE_GTK_INTEGRATION +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + delete _sniTrayIcon; +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION + + delete _trayIconMenuXEmbed; } } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index bc7eb98c1..b83132280 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -9,7 +9,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_main_window.h" -#include +#include "ui/widgets/popup_menu.h" + +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION +#include "statusnotifieritem.h" +#include +#endif namespace Platform { @@ -21,7 +26,12 @@ public: void psFirstShow(); - virtual QImage iconWithCounter(int size, int count, style::color bg, style::color fg, bool smallIcon) = 0; + virtual QImage iconWithCounter( + int size, + int count, + style::color bg, + style::color fg, + bool smallIcon) = 0; static void LibsLoaded(); @@ -30,9 +40,6 @@ public: public slots: void psShowTrayMenu(); - void psStatusIconCheck(); - void psUpdateIndicator(); - protected: void unreadCounterChangedHook() override; @@ -46,17 +53,26 @@ protected: void psTrayMenuUpdated(); void psSetupTrayIcon(); - virtual void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color) = 0; + virtual void placeSmallCounter( + QImage &img, + int size, + int count, + style::color bg, + const QPoint &shift, + style::color color) = 0; private: + Ui::PopupMenu *_trayIconMenuXEmbed = nullptr; + void updateIconCounters(); - void psCreateTrayIcon(); - QTimer _psCheckStatusIconTimer; - int _psCheckStatusIconLeft = 100; +#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION + StatusNotifierItem *_sniTrayIcon = nullptr; + std::unique_ptr _trayIconFile = nullptr; - QTimer _psUpdateIndicatorTimer; - crl::time _psLastIndicatorUpdate = 0; + void setSNITrayIcon(const QIcon &icon, const QPixmap &iconPixmap); + void attachToSNITrayIcon(); +#endif // !TDESKTOP_DISABLE_DBUS_INTEGRATION }; diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index e641c98ff..0ba17b6e7 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -44,7 +44,7 @@ namespace { constexpr auto kDesktopFile = ":/misc/telegramdesktop.desktop"_cs; -bool XDGDesktopPortalIsPresent = false; +bool XDGDesktopPortalPresent = false; #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION void SandboxAutostart(bool autostart) { @@ -196,13 +196,13 @@ bool InSnap() { } bool IsXDGDesktopPortalPresent() { - return XDGDesktopPortalIsPresent; -}; + return XDGDesktopPortalPresent; +} -QString CurrentExecutablePath(int argc, char *argv[]) { +QString ProcessNameByPID(const QString &pid) { constexpr auto kMaxPath = 1024; char result[kMaxPath] = { 0 }; - auto count = readlink("/proc/self/exe", result, kMaxPath); + auto count = readlink("/proc/" + pid.toLatin1() + "/exe", result, kMaxPath); if (count > 0) { auto filename = QFile::decodeName(result); auto deletedPostfix = qstr(" (deleted)"); @@ -212,26 +212,53 @@ QString CurrentExecutablePath(int argc, char *argv[]) { return filename; } + return QString(); +} + +QString CurrentExecutablePath(int argc, char *argv[]) { + const auto processName = ProcessNameByPID(qsl("self")); + // Fallback to the first command line argument. - return argc ? QFile::decodeName(argv[0]) : QString(); + return !processName.isEmpty() + ? processName + : argc + ? QFile::decodeName(argv[0]) + : QString(); +} + +QString AppRuntimeDirectory() { + static const auto RuntimeDirectory = [&] { + auto runtimeDir = QStandardPaths::writableLocation( + QStandardPaths::RuntimeLocation); + + if (InSandbox()) { + runtimeDir += qsl("/app/") + + QString::fromLatin1(qgetenv("FLATPAK_ID")); + } + + if (!QFileInfo::exists(runtimeDir)) { // non-systemd distros + runtimeDir = QDir::tempPath(); + } + + if (runtimeDir.isEmpty()) { + runtimeDir = qsl("/tmp/"); + } + + if (!runtimeDir.endsWith('/')) { + runtimeDir += '/'; + } + + return runtimeDir; + }(); + + return RuntimeDirectory; } QString SingleInstanceLocalServerName(const QString &hash) { - const auto runtimeDir = QStandardPaths::writableLocation( - QStandardPaths::RuntimeLocation); - - if (InSandbox()) { - return runtimeDir - + qsl("/app/") - + QString::fromLatin1(qgetenv("FLATPAK_ID")) - + '/' + hash; - } else if (QFileInfo::exists(runtimeDir) && InSnap()) { - return runtimeDir + '/' + hash; - } else if (QFileInfo::exists(runtimeDir)) { - return runtimeDir + '/' + hash + '-' + cGUIDStr(); - } else { // non-systemd distros - return QStandardPaths::writableLocation(QStandardPaths::TempLocation) - + '/' + hash + '-' + cGUIDStr(); + if (InSandbox() || InSnap()) { + return AppRuntimeDirectory() + hash; + } else { + return AppRuntimeDirectory() + hash + '-' + cGUIDStr(); } } @@ -381,12 +408,12 @@ void start() { #if !defined(TDESKTOP_DISABLE_DBUS_INTEGRATION) && defined(TDESKTOP_FORCE_GTK_FILE_DIALOG) LOG(("Checking for XDG Desktop Portal...")); - XDGDesktopPortalIsPresent = QDBusInterface( + XDGDesktopPortalPresent = QDBusInterface( "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop").isValid(); // this can give us a chance to use a proper file dialog for current session - if(XDGDesktopPortalIsPresent) { + if(XDGDesktopPortalPresent) { LOG(("XDG Desktop Portal is present!")); qputenv("QT_QPA_PLATFORMTHEME", "xdgdesktopportal"); } else { @@ -467,6 +494,8 @@ bool OpenSystemSettings(SystemSettingsType type) { add("kcmshell4 phonon"); } else if (DesktopEnvironment::IsGnome()) { add("gnome-control-center sound"); + } else if (DesktopEnvironment::IsMATE()) { + add("mate-volume-control"); } add("pavucontrol-qt"); add("pavucontrol"); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index 5836a33ad..3ce0640ff 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -25,8 +25,10 @@ bool InSnap(); bool IsXDGDesktopPortalPresent(); +QString ProcessNameByPID(const QString &pid); QString CurrentExecutablePath(int argc, char *argv[]); +QString AppRuntimeDirectory(); QString SingleInstanceLocalServerName(const QString &hash); QString GetLauncherBasename(); diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 993b94630..ed81efae5 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -548,6 +548,9 @@ void MainWindow::psShowTrayMenu() { } void MainWindow::psTrayMenuUpdated() { + if (trayIcon && trayIconMenu && trayIcon->contextMenu() != trayIconMenu) { + trayIcon->setContextMenu(trayIconMenu); + } } void MainWindow::psSetupTrayIcon() { diff --git a/Telegram/ThirdParty/libdbusmenu-qt b/Telegram/ThirdParty/libdbusmenu-qt new file mode 160000 index 000000000..75afa1003 --- /dev/null +++ b/Telegram/ThirdParty/libdbusmenu-qt @@ -0,0 +1 @@ +Subproject commit 75afa1003c1d0f6fdfa3a76ce2db689b49f86968 diff --git a/Telegram/ThirdParty/statusnotifieritem/dbustypes.cpp b/Telegram/ThirdParty/statusnotifieritem/dbustypes.cpp new file mode 100644 index 000000000..fbb63804e --- /dev/null +++ b/Telegram/ThirdParty/statusnotifieritem/dbustypes.cpp @@ -0,0 +1,75 @@ +/* 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 + * Paulo Lieuthier + * + * 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; +} diff --git a/Telegram/ThirdParty/statusnotifieritem/dbustypes.h b/Telegram/ThirdParty/statusnotifieritem/dbustypes.h new file mode 100644 index 000000000..af5fa5ce2 --- /dev/null +++ b/Telegram/ThirdParty/statusnotifieritem/dbustypes.h @@ -0,0 +1,60 @@ +/* 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 + * Paulo Lieuthier + * + * 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 + +#ifndef DBUSTYPES_H +#define DBUSTYPES_H + +struct IconPixmap { + int width; + int height; + QByteArray bytes; +}; + +typedef QList IconPixmapList; + +Q_DECLARE_METATYPE(IconPixmap) +Q_DECLARE_METATYPE(IconPixmapList) + +struct ToolTip { + QString iconName; + QList 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 diff --git a/Telegram/ThirdParty/statusnotifieritem/org.kde.StatusNotifierItem.xml b/Telegram/ThirdParty/statusnotifieritem/org.kde.StatusNotifierItem.xml new file mode 100644 index 000000000..0a563c549 --- /dev/null +++ b/Telegram/ThirdParty/statusnotifieritem/org.kde.StatusNotifierItem.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.cpp b/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.cpp new file mode 100644 index 000000000..96a3a537b --- /dev/null +++ b/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.cpp @@ -0,0 +1,331 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org/ + * + * Copyright: 2015 LXQt team + * Authors: + * Paulo Lieuthier + * + * 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 +#include +#include + +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(id), + mTitle(QLatin1String("Test")), + mStatus(QLatin1String("Active")), + 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() +{ + QDBusInterface interface(QLatin1String("org.kde.StatusNotifierWatcher"), + QLatin1String("/StatusNotifierWatcher"), + QLatin1String("org.kde.StatusNotifierWatcher"), + mSessionBus); + interface.asyncCall(QLatin1String("RegisterStatusNotifierItem"), mSessionBus.baseService()); +} + +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::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) + { + 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) +{ + QDBusInterface interface(QLatin1String("org.freedesktop.Notifications"), QLatin1String("/org/freedesktop/Notifications"), + QLatin1String("org.freedesktop.Notifications"), mSessionBus); + interface.call(QLatin1String("Notify"), mTitle, (uint) 0, iconName, title, + msg, QStringList(), QVariantMap(), secs); +} + +IconPixmapList StatusNotifierItem::iconToPixmapList(const QIcon& icon) +{ + IconPixmapList pixmapList; + + // long live KDE! + const QList 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(), +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + image.byteCount()); +#else + image.sizeInBytes()); +#endif + + // 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; +} diff --git a/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.h b/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.h new file mode 100644 index 000000000..3a689d11d --- /dev/null +++ b/Telegram/ThirdParty/statusnotifieritem/statusnotifieritem.h @@ -0,0 +1,185 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org/ + * + * Copyright: 2015 LXQt team + * Authors: + * Paulo Lieuthier + * + * 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 +#include +#include +#include + +#include "dbustypes.h" + +class StatusNotifierItemAdaptor; +class DBusMenuExporter; + +class StatusNotifierItem : public QObject +{ + Q_OBJECT + + 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); + + 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; + + // 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 diff --git a/Telegram/cmake/telegram_options.cmake b/Telegram/cmake/telegram_options.cmake index a2086de8a..6bac7e658 100644 --- a/Telegram/cmake/telegram_options.cmake +++ b/Telegram/cmake/telegram_options.cmake @@ -8,7 +8,7 @@ option(TDESKTOP_FORCE_GTK_FILE_DIALOG "Force using GTK file dialog (Linux only). option(TDESKTOP_DISABLE_REGISTER_CUSTOM_SCHEME "Disable automatic 'tg://' URL scheme handler registration." ${DESKTOP_APP_USE_PACKAGED}) option(TDESKTOP_DISABLE_NETWORK_PROXY "Disable all code for working through Socks5 or MTProxy." OFF) option(TDESKTOP_DISABLE_DESKTOP_FILE_GENERATION "Disable automatic '.desktop' file generation (Linux only)." ${DESKTOP_APP_USE_PACKAGED}) -option(TDESKTOP_DISABLE_GTK_INTEGRATION "Disable all code for GTK integration (Linux only)." OFF) +option(TDESKTOP_DISABLE_GTK_INTEGRATION "Disable all code for GTK integration (Linux only)." ON) option(TDESKTOP_DISABLE_DBUS_INTEGRATION "Disable all code for D-Bus integration (Linux only)." OFF) option(TDESKTOP_USE_PACKAGED_TGVOIP "Find libtgvoip using CMake instead of bundled one." ${DESKTOP_APP_USE_PACKAGED}) option(TDESKTOP_API_TEST "Use test API credentials." OFF) @@ -48,8 +48,8 @@ if (NOT DESKTOP_APP_SPECIAL_TARGET STREQUAL "") set(TDESKTOP_FORCE_GTK_FILE_DIALOG ON) endif() -if (TDESKTOP_FORCE_GTK_FILE_DIALOG AND TDESKTOP_DISABLE_GTK_INTEGRATION) - message(FATAL_ERROR "Option TDESKTOP_FORCE_GTK_FILE_DIALOG conflicts with option TDESKTOP_DISABLE_GTK_INTEGRATION.") +if (TDESKTOP_FORCE_GTK_FILE_DIALOG) + set(TDESKTOP_DISABLE_GTK_INTEGRATION OFF) endif() if (DESKTOP_APP_DISABLE_SPELLCHECK) diff --git a/docs/building-cmake.md b/docs/building-cmake.md index 14b3e8fd9..e903f2694 100644 --- a/docs/building-cmake.md +++ b/docs/building-cmake.md @@ -14,7 +14,7 @@ You will need GCC 8 installed. To install them and all the required dependencies sudo apt-get install software-properties-common -y && \ sudo apt-get install git libexif-dev liblzma-dev libz-dev libssl-dev \ - libappindicator-dev libicu-dev libdee-dev libdrm-dev dh-autoreconf \ + libgtk2.0-dev libice-dev libsm-dev libicu-dev libdrm-dev dh-autoreconf \ autoconf automake build-essential libass-dev libfreetype6-dev \ libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev \ libvorbis-dev libenchant-dev libxcb1-dev libxcb-image0-dev libxcb-shm0-dev \ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index fff251a21..cc93dbba4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -52,15 +52,13 @@ parts: - g++-8 - qtbase5-private-dev - libmapbox-variant-dev - - libmsgsl-dev - - libayatana-appindicator3-dev - - libgtk-3-dev - libasound2-dev - libavcodec-dev - libavformat-dev - libavutil-dev - libswscale-dev - libswresample-dev + - libdbusmenu-qt5-dev - liblz4-dev - liblzma-dev - libminizip-dev @@ -71,13 +69,13 @@ parts: - zlib1g-dev stage-packages: - qt5-image-formats-plugins - - libayatana-appindicator3-1 - libasound2 - libavcodec57 - libavformat57 - libavutil55 - libswscale4 - libswresample2 + - libdbusmenu-qt5-2 - liblz4-1 - liblzma5 - libminizip1 @@ -116,6 +114,7 @@ parts: - cmake - desktop-qt5 - enchant + - gsl - range-v3 - xxhash @@ -205,6 +204,15 @@ parts: - --enable-relocatable prime: [-./bin/*] + gsl: + source: https://github.com/microsoft/GSL.git + source-depth: 1 + source-tag: v2.1.0 + plugin: cmake + configflags: + - -DGSL_TEST=OFF + prime: [-./*] + range-v3: source: https://github.com/ericniebler/range-v3.git source-depth: 1