diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index fa716c1dc..995c39d0f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -93,6 +93,7 @@ if (LINUX) target_link_libraries(Telegram PRIVATE desktop-app::external_statusnotifieritem + desktop-app::external_dbusmenu_qt ) endif() diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp index ba521184b..16db763d8 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include using namespace KWayland::Client; @@ -36,6 +37,10 @@ public: return _plasmaShell.get(); } + [[nodiscard]] AppMenuManager *appMenuManager() { + return _appMenuManager.get(); + } + [[nodiscard]] QEventLoop &interfacesLoop() { return _interfacesLoop; } @@ -51,6 +56,7 @@ private: Registry _applicationRegistry; std::unique_ptr _xdgExporter; std::unique_ptr _plasmaShell; + std::unique_ptr _appMenuManager; QEventLoop _interfacesLoop; bool _interfacesAnnounced = false; }; @@ -117,6 +123,21 @@ WaylandIntegration::Private::Private() &PlasmaShell::destroy); }); + connect( + &_applicationRegistry, + &Registry::appMenuAnnounced, + [=](uint name, uint version) { + _appMenuManager = std::unique_ptr{ + _applicationRegistry.createAppMenuManager(name, version), + }; + + connect( + _applicationConnection, + &ConnectionThread::connectionDied, + _appMenuManager.get(), + &AppMenuManager::destroy); + }); + _connection.initConnection(); } @@ -187,5 +208,27 @@ void WaylandIntegration::skipTaskbar(QWindow *window, bool skip) { plasmaSurface->setSkipTaskbar(skip); } +void WaylandIntegration::registerAppMenu( + QWindow *window, + const QString &serviceName, + const QString &objectPath) { + const auto manager = _private->appMenuManager(); + if (!manager) { + return; + } + + const auto surface = Surface::fromWindow(window); + if (!surface) { + return; + } + + const auto appMenu = manager->create(surface, surface); + if (!appMenu) { + return; + } + + appMenu->setAddress(serviceName, objectPath); +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h index 3c6cd04a2..a37f10e34 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h @@ -18,6 +18,10 @@ public: [[nodiscard]] QString nativeHandle(QWindow *window); [[nodiscard]] bool skipTaskbarSupported(); void skipTaskbar(QWindow *window, bool skip); + void registerAppMenu( + QWindow *window, + const QString &serviceName, + const QString &objectPath); private: WaylandIntegration(); diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp index 74e6485dc..7ab26b1e2 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp @@ -44,5 +44,11 @@ bool WaylandIntegration::skipTaskbarSupported() { void WaylandIntegration::skipTaskbar(QWindow *window, bool skip) { } +void WaylandIntegration::registerAppMenu( + QWindow *window, + const QString &serviceName, + const QString &objectPath) { +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index fa200ddb8..f44390860 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -43,12 +43,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION +#include +#include +#include #include #include +#include #include #include @@ -71,6 +74,12 @@ 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; + +constexpr auto kMainMenuObjectPath = "/MenuBar"_cs; + bool TrayIconMuted = true; int32 TrayIconCount = 0; base::flat_map TrayIconImageBack; @@ -536,6 +545,65 @@ uint djbStringHash(const std::string &string) { } return hash; } + +bool IsAppMenuSupported() { + try { + const auto connection = Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION); + + return base::Platform::DBus::NameHasOwner( + connection, + std::string(kAppMenuService)); + } catch (...) { + } + + return false; +} + +// This call must be made from the same bus connection as DBusMenuExporter +// So it must use QDBusConnection +void RegisterAppMenu(QWindow *window, const QString &menuPath) { + if (const auto integration = WaylandIntegration::Instance()) { + integration->registerAppMenu( + window, + QDBusConnection::sessionBus().baseService(), + menuPath); + return; + } + + auto message = QDBusMessage::createMethodCall( + kAppMenuService.utf16(), + kAppMenuObjectPath.utf16(), + kAppMenuInterface.utf16(), + qsl("RegisterWindow")); + + message.setArguments({ + window->winId(), + QVariant::fromValue(QDBusObjectPath(menuPath)) + }); + + QDBusConnection::sessionBus().send(message); +} + +// This call must be made from the same bus connection as DBusMenuExporter +// So it must use QDBusConnection +void UnregisterAppMenu(QWindow *window) { + if (const auto integration = WaylandIntegration::Instance()) { + return; + } + + auto message = QDBusMessage::createMethodCall( + kAppMenuService.utf16(), + kAppMenuObjectPath.utf16(), + kAppMenuInterface.utf16(), + qsl("UnregisterWindow")); + + message.setArguments({ + window->winId() + }); + + QDBusConnection::sessionBus().send(message); +} #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION } // namespace @@ -554,6 +622,10 @@ public: uint sniWatcherId = 0; std::unique_ptr trayIconFile; + bool appMenuSupported = false; + uint appMenuWatcherId = 0; + DBusMenuExporter *mainMenuExporter = nullptr; + void setSNITrayIcon(int counter, bool muted); void attachToSNITrayIcon(); void handleSNIHostRegistered(); @@ -562,6 +634,11 @@ public: 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 private: @@ -686,6 +763,25 @@ void MainWindow::Private::handleSNIOwnerChanged( (Core::App().settings().workMode() == WorkMode::TrayOnly) && _public->trayAvailable()); } + +void MainWindow::Private::handleAppMenuOwnerChanged( + const QString &service, + const QString &oldOwner, + const QString &newOwner) { + if (oldOwner.isEmpty() && !newOwner.isEmpty()) { + appMenuSupported = true; + LOG(("Using D-Bus global menu.")); + } else if (!oldOwner.isEmpty() && newOwner.isEmpty()) { + appMenuSupported = false; + LOG(("Not using D-Bus global menu.")); + } + + if (appMenuSupported && mainMenuExporter) { + RegisterAppMenu(_public->windowHandle(), kMainMenuObjectPath.utf16()); + } else { + UnregisterAppMenu(_public->windowHandle()); + } +} #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION MainWindow::MainWindow(not_null controller) @@ -722,6 +818,7 @@ void MainWindow::initHook() { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION _sniAvailable = IsSNIAvailable(); + _private->appMenuSupported = IsAppMenuSupported(); try { _private->dbusConnection = Gio::DBus::Connection::get_sync( @@ -760,9 +857,28 @@ void MainWindow::initHook() { QString::fromStdString(oldOwner), QString::fromStdString(newOwner)); }); + + _private->appMenuWatcherId = base::Platform::DBus::RegisterServiceWatcher( + _private->dbusConnection, + std::string(kAppMenuService), + [=]( + const Glib::ustring &service, + const Glib::ustring &oldOwner, + const Glib::ustring &newOwner) { + _private->handleAppMenuOwnerChanged( + QString::fromStdString(service), + QString::fromStdString(oldOwner), + QString::fromStdString(newOwner)); + }); } catch (...) { } + if (_private->appMenuSupported) { + LOG(("Using D-Bus global menu.")); + } else { + LOG(("Not using D-Bus global menu.")); + } + if (UseUnityCounter()) { LOG(("Using Unity launcher counter.")); } else { @@ -926,8 +1042,7 @@ void MainWindow::createGlobalMenu() { } }; - psMainMenu = new QMenuBar(this); - psMainMenu->hide(); + psMainMenu = new QMenu(this); auto file = psMainMenu->addMenu(tr::lng_mac_menu_file(tr::now)); @@ -1104,6 +1219,16 @@ void MainWindow::createGlobalMenu() { about->setMenuRole(QAction::AboutQtRole); +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION + _private->mainMenuExporter = new DBusMenuExporter( + kMainMenuObjectPath.utf16(), + psMainMenu); + + if (_private->appMenuSupported) { + RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16()); + } +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION + updateGlobalMenu(); } @@ -1229,6 +1354,16 @@ void MainWindow::handleNativeSurfaceChanged(bool exist) { (Core::App().settings().workMode() == WorkMode::TrayOnly) && trayAvailable()); } + +#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION + if (_private->appMenuSupported && _private->mainMenuExporter) { + if (exist) { + RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16()); + } else { + UnregisterAppMenu(windowHandle()); + } + } +#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION } MainWindow::~MainWindow() { @@ -1243,6 +1378,15 @@ MainWindow::~MainWindow() { _private->dbusConnection->signal_unsubscribe( _private->sniWatcherId); } + + if (_private->appMenuWatcherId != 0) { + _private->dbusConnection->signal_unsubscribe( + _private->appMenuWatcherId); + } + } + + if (_private->appMenuSupported) { + UnregisterAppMenu(windowHandle()); } #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index ebdf560e6..7bfb6f0cf 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -10,8 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_main_window.h" #include "base/unique_qptr.h" -class QMenuBar; - namespace Ui { class PopupMenu; } // namespace Ui @@ -72,7 +70,7 @@ private: bool _sniAvailable = false; base::unique_qptr _trayIconMenuXEmbed; - QMenuBar *psMainMenu = nullptr; + QMenu *psMainMenu = nullptr; QAction *psLogout = nullptr; QAction *psUndo = nullptr; QAction *psRedo = nullptr;