From 551ea7d8791e666d3e268ad43aa5ae142bfc2cb6 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 23 Jun 2021 03:20:40 +0400 Subject: [PATCH] Move GTK integration out of process with D-Bus --- .github/workflows/linux.yml | 3 + Telegram/CMakeLists.txt | 1 + Telegram/SourceFiles/core/launcher.h | 2 +- .../platform/linux/launcher_linux.cpp | 30 +- .../platform/linux/launcher_linux.h | 4 + .../platform/linux/linux_gdk_helper.cpp | 57 +- .../platform/linux/linux_gdk_helper.h | 3 +- .../platform/linux/linux_gtk_integration.cpp | 575 +++++++++++++++++- .../platform/linux/linux_gtk_integration.h | 25 +- .../linux/linux_gtk_integration_dummy.cpp | 29 +- .../linux/linux_gtk_open_with_dialog.cpp | 54 +- .../linux/linux_gtk_open_with_dialog.h | 6 +- .../platform/linux/specific_linux.cpp | 14 +- 13 files changed, 729 insertions(+), 74 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e069a53e4e..bd232a8719 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -93,6 +93,9 @@ jobs: DEFINE="" if [ -n "${{ matrix.defines }}" ]; then DEFINE="-D ${{ matrix.defines }}=ON" + if [ "${{ matrix.defines }}" == "DESKTOP_APP_DISABLE_DBUS_INTEGRATION" ]; then + DEFINE="$DEFINE -D DESKTOP_APP_DISABLE_GTK_INTEGRATION=ON" + fi echo Define from matrix: $DEFINE echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV else diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6b4b687448..fa716c1dc5 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -112,6 +112,7 @@ if (LINUX) endif() if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION) + target_link_libraries(Telegram PRIVATE rt) find_package(PkgConfig REQUIRED) if (DESKTOP_APP_USE_PACKAGED AND NOT DESKTOP_APP_USE_PACKAGED_LAZY) diff --git a/Telegram/SourceFiles/core/launcher.h b/Telegram/SourceFiles/core/launcher.h index 0d6959b0c2..c97fb732ef 100644 --- a/Telegram/SourceFiles/core/launcher.h +++ b/Telegram/SourceFiles/core/launcher.h @@ -19,7 +19,7 @@ public: static std::unique_ptr Create(int argc, char *argv[]); - int exec(); + virtual int exec(); QString argumentsString() const; bool customWorkingDir() const; diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index f358fbaec2..dc7f2f6952 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/crash_reports.h" #include "core/update_checker.h" +#include "platform/linux/linux_gtk_integration.h" #include @@ -23,6 +24,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Platform { namespace { +using Platform::internal::GtkIntegration; + class Arguments { public: void push(QByteArray argument) { @@ -45,7 +48,32 @@ private: } // namespace Launcher::Launcher(int argc, char *argv[]) -: Core::Launcher(argc, argv) { +: Core::Launcher(argc, argv) +, _arguments(argv, argv + argc) { +} + +int Launcher::exec() { + for (auto i = begin(_arguments), e = end(_arguments); i != e; ++i) { + if (*i == "-basegtkintegration" && std::distance(i, e) > 2) { + return GtkIntegration::Exec( + GtkIntegration::Type::Base, + QString::fromStdString(*(i + 1)), + std::stoi(*(i + 2))); + } else if (*i == "-webviewhelper" && std::distance(i, e) > 3) { + return GtkIntegration::Exec( + GtkIntegration::Type::Webview, + QString::fromStdString(*(i + 1)), + std::stoi(*(i + 2)), + std::stoi(*(i + 3))); + } else if (*i == "-gtkintegration" && std::distance(i, e) > 2) { + return GtkIntegration::Exec( + GtkIntegration::Type::TDesktop, + QString::fromStdString(*(i + 1)), + std::stoi(*(i + 2))); + } + } + + return Core::Launcher::exec(); } void Launcher::initHook() { diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.h b/Telegram/SourceFiles/platform/linux/launcher_linux.h index 188b1b6e6d..782c2c12d1 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.h +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.h @@ -15,10 +15,14 @@ class Launcher : public Core::Launcher { public: Launcher(int argc, char *argv[]); + int exec(); + private: void initHook() override; bool launchUpdater(UpdaterLaunch action) override; + std::vector _arguments; + }; } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp index c19149fc28..d49f9ea2c6 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp @@ -10,9 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/linux/base_linux_gtk_integration.h" #include "base/platform/linux/base_linux_gtk_integration_p.h" #include "platform/linux/linux_gtk_integration_p.h" -#include "platform/linux/linux_wayland_integration.h" - -#include #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION extern "C" { @@ -98,21 +95,17 @@ void GdkHelperLoad(QLibrary &lib) { } } -void GdkSetTransientFor(GdkWindow *window, QWindow *parent) { +void GdkSetTransientFor(GdkWindow *window, const QString &parent) { #ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION if (gdk_wayland_window_get_type != nullptr && gdk_wayland_window_set_transient_for_exported != nullptr - && GDK_IS_WAYLAND_WINDOW(window)) { - if (const auto integration = WaylandIntegration::Instance()) { - if (const auto handle = integration->nativeHandle(parent) - ; !handle.isEmpty()) { - auto handleUtf8 = handle.toUtf8(); - gdk_wayland_window_set_transient_for_exported( - window, - handleUtf8.data()); - return; - } - } + && GDK_IS_WAYLAND_WINDOW(window) + && parent.startsWith("wayland:")) { + auto handle = parent.mid(8).toUtf8(); + gdk_wayland_window_set_transient_for_exported( + window, + handle.data()); + return; } #endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION @@ -121,23 +114,33 @@ void GdkSetTransientFor(GdkWindow *window, QWindow *parent) { && gdk_x11_display_get_xdisplay != nullptr && gdk_x11_window_get_xid != nullptr && gdk_window_get_display != nullptr - && GDK_IS_X11_WINDOW(window)) { - XSetTransientForHint( - gdk_x11_display_get_xdisplay(gdk_window_get_display(window)), - gdk_x11_window_get_xid(window), - parent->winId()); - return; + && GDK_IS_X11_WINDOW(window) + && parent.startsWith("x11:")) { + auto ok = false; + const auto winId = parent.mid(4).toInt(&ok, 16); + if (ok) { + XSetTransientForHint( + gdk_x11_display_get_xdisplay(gdk_window_get_display(window)), + gdk_x11_window_get_xid(window), + winId); + return; + } } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION if (gdk_x11_drawable_get_xdisplay != nullptr - && gdk_x11_drawable_get_xid != nullptr) { - XSetTransientForHint( - gdk_x11_drawable_get_xdisplay(window), - gdk_x11_drawable_get_xid(window), - parent->winId()); - return; + && gdk_x11_drawable_get_xid != nullptr + && parent.startsWith("x11:")) { + auto ok = false; + const auto winId = parent.mid(4).toInt(&ok, 16); + if (ok) { + XSetTransientForHint( + gdk_x11_drawable_get_xdisplay(window), + gdk_x11_drawable_get_xid(window), + winId); + return; + } } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION } diff --git a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h index df06803c9e..ce46094bba 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h +++ b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once class QLibrary; -class QWindow; extern "C" { #include @@ -19,7 +18,7 @@ namespace Platform { namespace internal { void GdkHelperLoad(QLibrary &lib); -void GdkSetTransientFor(GdkWindow *window, QWindow *parent); +void GdkSetTransientFor(GdkWindow *window, const QString &parent); } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp index 13cbe3f31c..0504dea56c 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_integration.cpp @@ -7,11 +7,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/linux_gtk_integration.h" +#ifdef DESKTOP_APP_DISABLE_DBUS_INTEGRATION +#error "GTK integration depends on D-Bus integration." +#endif // DESKTOP_APP_DISABLE_DBUS_INTEGRATION + #include "base/platform/linux/base_linux_gtk_integration.h" #include "base/platform/linux/base_linux_gtk_integration_p.h" +#include "base/platform/linux/base_linux_glibmm_helper.h" +#include "base/platform/linux/base_linux_dbus_utilities.h" +#include "base/platform/base_platform_info.h" #include "platform/linux/linux_gtk_integration_p.h" #include "platform/linux/linux_gdk_helper.h" #include "platform/linux/linux_gtk_open_with_dialog.h" +#include "platform/linux/linux_wayland_integration.h" +#include "webview/webview_interface.h" +#include "window/window_controller.h" +#include "core/application.h" + +#include + +#include +#include + +#include +#include +#include namespace Platform { namespace internal { @@ -21,6 +41,31 @@ using BaseGtkIntegration = base::Platform::GtkIntegration; namespace { +constexpr auto kService = "org.telegram.desktop.GtkIntegration-%1"_cs; +constexpr auto kObjectPath = "/org/telegram/desktop/GtkIntegration"_cs; +constexpr auto kInterface = "org.telegram.desktop.GtkIntegration"_cs; +constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs; +constexpr auto kGifcShmId = "tdesktop-gtk-gifc"_cs; + +constexpr auto kIntrospectionXML = R"INTROSPECTION( + + + + + + + + + + + + + + + + +)INTROSPECTION"_cs; + bool GetImageFromClipboardSupported() { return (gtk_clipboard_get != nullptr) && (gtk_clipboard_wait_for_contents != nullptr) @@ -37,7 +82,182 @@ bool GetImageFromClipboardSupported() { } // namespace -GtkIntegration::GtkIntegration() { +class GtkIntegration::Private { +public: + Private() + : dbusConnection([] { + try { + return Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION); + } catch (...) { + return Glib::RefPtr(); + } + }()) + , interfaceVTable(sigc::mem_fun(this, &Private::handleMethodCall)) + , serviceName(kService.utf16().arg(getpid()).toStdString()) { + } + + void handleMethodCall( + const Glib::RefPtr &connection, + const Glib::ustring &sender, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &method_name, + const Glib::VariantContainerBase ¶meters, + const Glib::RefPtr &invocation); + + const Glib::RefPtr dbusConnection; + const Gio::DBus::InterfaceVTable interfaceVTable; + Glib::RefPtr introspectionData; + Glib::ustring serviceName; + Glib::ustring parentDBusName; + bool remoting = true; + uint registerId = 0; + uint parentServiceWatcherId = 0; +}; + +void GtkIntegration::Private::handleMethodCall( + const Glib::RefPtr &connection, + const Glib::ustring &sender, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &method_name, + const Glib::VariantContainerBase ¶meters, + const Glib::RefPtr &invocation) { + if (sender != parentDBusName) { + Gio::DBus::Error error( + Gio::DBus::Error::ACCESS_DENIED, + "Access denied."); + + invocation->return_error(error); + } + + try { + const auto integration = Instance(); + if (!integration) { + throw std::exception(); + } + + auto parametersCopy = parameters; + + if (method_name == "Load") { + const auto allowedBackends = base::Platform::GlibVariantCast< + Glib::ustring>(parametersCopy.get_child(0)); + + integration->load(QString::fromStdString(allowedBackends)); + invocation->return_value({}); + return; + } else if (method_name == "ShowOpenWithDialog") { + const auto parent = base::Platform::GlibVariantCast< + Glib::ustring>(parametersCopy.get_child(0)); + + const auto filepath = base::Platform::GlibVariantCast< + Glib::ustring>(parametersCopy.get_child(1)); + + const auto result = File::internal::ShowGtkOpenWithDialog( + QString::fromStdString(parent), + QString::fromStdString(filepath)); + + if (result) { + invocation->return_value({}); + return; + } + } else if (method_name == "GetImageFromClipboard") { + const auto image = integration->getImageFromClipboard(); + if (!image.isNull()) { + const auto bitsPerSample = 8; + const auto channels = image.hasAlphaChannel() ? 4 : 3; + + QVector dataVector( + image.constBits(), + image.constBits() + image.sizeInBytes()); + + QByteArray streamData; + QDataStream stream(&streamData, QIODevice::WriteOnly); + stream + << image.width() + << image.height() + << image.bytesPerLine() + << image.hasAlphaChannel() + << bitsPerSample + << channels + << dataVector; + + const auto fd = shm_open( + kGifcShmId.utf8().constData(), + O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR); + + if (fd == -1) { + throw std::exception(); + } + + const auto fdGuard = gsl::finally([&] { + close(fd); + shm_unlink(kGifcShmId.utf8().constData()); + }); + + if (ftruncate(fd, streamData.size())) { + throw std::exception(); + } + + const auto mapped = mmap( + nullptr, + streamData.size(), + PROT_WRITE, + MAP_SHARED, + fd, + 0); + + if (mapped == MAP_FAILED) { + throw std::exception(); + } + + const auto mappedGuard = gsl::finally([&] { + munmap(mapped, streamData.size()); + }); + + memcpy(mapped, streamData.constData(), streamData.size()); + + const auto fdList = Gio::UnixFDList::create(); + fdList->append(fd); + + invocation->return_value( + Glib::VariantContainerBase::create_tuple({ + Glib::wrap(g_variant_new_handle(0)), + Glib::Variant::create(streamData.size()), + }), + fdList); + + return; + } + } + } catch (...) { + } + + Gio::DBus::Error error( + Gio::DBus::Error::UNKNOWN_METHOD, + "Method does not exist."); + + invocation->return_error(error); +} + +GtkIntegration::GtkIntegration() +: _private(std::make_unique()) { +} + +GtkIntegration::~GtkIntegration() { + if (_private->dbusConnection) { + if (_private->parentServiceWatcherId != 0) { + _private->dbusConnection->signal_unsubscribe( + _private->parentServiceWatcherId); + } + + if (_private->registerId != 0) { + _private->dbusConnection->unregister_object( + _private->registerId); + } + } } GtkIntegration *GtkIntegration::Instance() { @@ -49,10 +269,31 @@ GtkIntegration *GtkIntegration::Instance() { return &instance; } -void GtkIntegration::load() { +void GtkIntegration::load(const QString &allowedBackends) { static bool Loaded = false; Expects(!Loaded); + if (_private->remoting) { + if (!_private->dbusConnection) { + return; + } + + try { + auto reply = _private->dbusConnection->call_sync( + std::string(kObjectPath), + std::string(kInterface), + "Load", + base::Platform::MakeGlibVariant(std::tuple{ + Glib::ustring(allowedBackends.toStdString()), + }), + _private->serviceName); + } catch (...) { + } + + return; + } + + BaseGtkIntegration::Instance()->load(allowedBackends, true); if (!BaseGtkIntegration::Instance()->loaded()) { return; } @@ -86,13 +327,233 @@ void GtkIntegration::load() { Loaded = true; } +int GtkIntegration::exec(const QString &parentDBusName, int ppid) { + _private->remoting = false; + _private->serviceName = kService.utf16().arg(ppid).toStdString(); + _private->parentDBusName = parentDBusName.toStdString(); + + _private->introspectionData = Gio::DBus::NodeInfo::create_for_xml( + std::string(kIntrospectionXML)); + + _private->registerId = _private->dbusConnection->register_object( + std::string(kObjectPath), + _private->introspectionData->lookup_interface(), + _private->interfaceVTable); + + rpl::lifetime lifetime; + + File::internal::GtkOpenWithDialogResponse( + ) | rpl::start_with_next([=](bool response) { + try { + _private->dbusConnection->emit_signal( + std::string(kObjectPath), + std::string(kInterface), + "OpenWithDialogResponse", + _private->parentDBusName, + base::Platform::MakeGlibVariant(std::tuple{ + response, + })); + } catch (...) { + } + }, lifetime); + + const auto app = Gio::Application::create(_private->serviceName); + app->hold(); + _private->parentServiceWatcherId = base::Platform::DBus::RegisterServiceWatcher( + _private->dbusConnection, + parentDBusName.toStdString(), + [=]( + const Glib::ustring &service, + const Glib::ustring &oldOwner, + const Glib::ustring &newOwner) { + if (!newOwner.empty()) { + return; + } + app->quit(); + }); + return app->run(0, nullptr); +} + bool GtkIntegration::showOpenWithDialog(const QString &filepath) const { - return File::internal::ShowGtkOpenWithDialog(filepath); + const auto parent = [&] { + if (const auto activeWindow = Core::App().activeWindow()) { + if (const auto integration = WaylandIntegration::Instance()) { + if (const auto handle = integration->nativeHandle( + activeWindow->widget()->windowHandle()) + ; !handle.isEmpty()) { + return qsl("wayland:") + handle; + } + } else if (Platform::IsX11()) { + return qsl("x11:") + QString::number( + activeWindow->widget()->winId(), + 16); + } + } + return QString(); + }(); + + if (_private->remoting) { + if (!_private->dbusConnection) { + return false; + } + + try { + _private->dbusConnection->call_sync( + std::string(kObjectPath), + std::string(kInterface), + "ShowOpenWithDialog", + base::Platform::MakeGlibVariant(std::tuple{ + Glib::ustring(parent.toStdString()), + Glib::ustring(filepath.toStdString()), + }), + _private->serviceName); + + const auto context = Glib::MainContext::create(); + const auto loop = Glib::MainLoop::create(context); + g_main_context_push_thread_default(context->gobj()); + bool result = false; + + const auto signalId = _private->dbusConnection->signal_subscribe( + [&]( + const Glib::RefPtr &connection, + const Glib::ustring &sender_name, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &signal_name, + Glib::VariantContainerBase parameters) { + try { + auto parametersCopy = parameters; + + result = base::Platform::GlibVariantCast( + parametersCopy.get_child(0)); + + loop->quit(); + } catch (...) { + } + }, + _private->serviceName, + std::string(kInterface), + "OpenWithDialogResponse", + std::string(kObjectPath)); + + const auto signalGuard = gsl::finally([&] { + if (signalId != 0) { + _private->dbusConnection->signal_unsubscribe(signalId); + } + }); + + QWindow window; + QGuiApplicationPrivate::showModalWindow(&window); + loop->run(); + g_main_context_pop_thread_default(context->gobj()); + QGuiApplicationPrivate::hideModalWindow(&window); + + return result; + } catch (...) { + } + + return false; + } + + if (!File::internal::ShowGtkOpenWithDialog(parent, filepath)) { + return false; + } + + const auto context = Glib::MainContext::create(); + const auto loop = Glib::MainLoop::create(context); + g_main_context_push_thread_default(context->gobj()); + rpl::lifetime lifetime; + bool result = false; + + File::internal::GtkOpenWithDialogResponse( + ) | rpl::start_with_next([&](bool response) { + result = response; + loop->quit(); + }, lifetime); + + QWindow window; + QGuiApplicationPrivate::showModalWindow(&window); + loop->run(); + g_main_context_pop_thread_default(context->gobj()); + QGuiApplicationPrivate::hideModalWindow(&window); + + return result; } QImage GtkIntegration::getImageFromClipboard() const { QImage data; + if (_private->remoting) { + if (!_private->dbusConnection) { + return data; + } + + try { + Glib::RefPtr outFdList; + + auto reply = _private->dbusConnection->call_sync( + std::string(kObjectPath), + std::string(kInterface), + "GetImageFromClipboard", + {}, + {}, + outFdList, + _private->serviceName); + + const auto streamSize = base::Platform::GlibVariantCast( + reply.get_child(1)); + + const auto mapped = mmap( + nullptr, + streamSize, + PROT_READ, + MAP_SHARED, + outFdList->get(0), + 0); + + if (mapped == MAP_FAILED) { + return data; + } + + QByteArray streamData; + streamData.resize(streamSize); + memcpy(streamData.data(), mapped, streamData.size()); + munmap(mapped, streamData.size()); + + int imageWidth = 0; + int imageHeight = 0; + int imageBytesPerLine = 0; + bool imageHasAlphaChannel = false; + int imageBitsPerSample = 0; + int imageChannels = 0; + QVector imageData; + + QDataStream stream(streamData); + stream + >> imageWidth + >> imageHeight + >> imageBytesPerLine + >> imageHasAlphaChannel + >> imageBitsPerSample + >> imageChannels + >> imageData; + + data = QImage( + imageData.data(), + imageWidth, + imageHeight, + imageBytesPerLine, + imageHasAlphaChannel + ? QImage::Format_RGBA8888 + : QImage::Format_RGB888).copy(); + + return data; + } catch (...) { + } + + return data; + } + if (!GetImageFromClipboardSupported()) { return data; } @@ -130,5 +591,113 @@ QImage GtkIntegration::getImageFromClipboard() const { return data; } +QString GtkIntegration::AllowedBackends() { + return Platform::IsWayland() + ? qsl("wayland,x11") + : Platform::IsX11() + ? qsl("x11,wayland") + : QString(); +} + +int GtkIntegration::Exec( + Type type, + const QString &parentDBusName, + int ppid, + uint instanceNumber) { + Glib::init(); + Gio::init(); + + if (type == Type::Base) { + if (const auto integration = BaseGtkIntegration::Instance()) { + return integration->exec(parentDBusName, ppid); + } + } else if (type == Type::Webview) { + if (const auto instance = Webview::CreateInstance({})) { + return instance->exec( + parentDBusName.toStdString(), + ppid, + instanceNumber); + } + } else if (type == Type::TDesktop) { + if (const auto integration = Instance()) { + return integration->exec(parentDBusName, ppid); + } + } + + return 1; +} + +void GtkIntegration::Start(Type type) { + if (type != Type::Base && type != Type::TDesktop) { + return; + } + + const auto dbusName = [] { + try { + static const auto connection = Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION); + + return QString::fromStdString(connection->get_unique_name()); + } catch (...) { + return QString(); + } + }(); + + if (dbusName.isEmpty()) { + return; + } + + QProcess::startDetached(cExeDir() + cExeName(), { + (type == Type::Base) + ? qsl("-basegtkintegration") + : qsl("-gtkintegration"), + dbusName, + QString::number(getpid()), + }); +} + +void GtkIntegration::Autorestart(Type type) { + if (type != Type::Base && type != Type::TDesktop) { + return; + } + + try { + static const auto connection = Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION); + + const auto baseServiceName = [] { + if (const auto integration = BaseGtkIntegration::Instance()) { + return integration->serviceName(); + } + return QString(); + }(); + + base::Platform::DBus::RegisterServiceWatcher( + connection, + (type == Type::Base) + ? baseServiceName.toStdString() + : kService.utf16().arg(getpid()).toStdString(), + [=]( + const Glib::ustring &service, + const Glib::ustring &oldOwner, + const Glib::ustring &newOwner) { + if (newOwner.empty()) { + Start(type); + } else { + if (type == Type::Base) { + if (const auto integration = BaseGtkIntegration::Instance()) { + integration->load(AllowedBackends()); + } + } else if (type == Type::TDesktop) { + if (const auto integration = Instance()) { + integration->load(AllowedBackends()); + } + } + } + }); + } catch (...) { + } +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_integration.h b/Telegram/SourceFiles/platform/linux/linux_gtk_integration.h index 15f7405a44..df24fd2c01 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gtk_integration.h +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_integration.h @@ -12,16 +12,39 @@ namespace internal { class GtkIntegration { public: + enum class Type { + Base, + Webview, + TDesktop, + }; + static GtkIntegration *Instance(); - void load(); + void load(const QString &allowedBackends); + int exec(const QString &parentDBusName, int ppid); [[nodiscard]] bool showOpenWithDialog(const QString &filepath) const; [[nodiscard]] QImage getImageFromClipboard() const; + static QString AllowedBackends(); + + static int Exec( + Type type, + const QString &parentDBusName, + int ppid, + uint instanceNumber = 0); + + static void Start(Type type); + + static void Autorestart(Type type); + private: GtkIntegration(); + ~GtkIntegration(); + + class Private; + const std::unique_ptr _private; }; } // namespace internal diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_integration_dummy.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_integration_dummy.cpp index 93feeaf738..2cd7c2a03a 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gtk_integration_dummy.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_integration_dummy.cpp @@ -10,14 +10,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Platform { namespace internal { +class GtkIntegration::Private { +}; + GtkIntegration::GtkIntegration() { } +GtkIntegration::~GtkIntegration() = default; + GtkIntegration *GtkIntegration::Instance() { return nullptr; } -void GtkIntegration::load() { +void GtkIntegration::load(const QString &allowedBackends) { +} + +int GtkIntegration::exec(const QString &parentDBusName, int ppid) { + return 1; } bool GtkIntegration::showOpenWithDialog(const QString &filepath) const { @@ -28,5 +37,23 @@ QImage GtkIntegration::getImageFromClipboard() const { return {}; } +QString GtkIntegration::AllowedBackends() { + return {}; +} + +int GtkIntegration:Exec( + Type type, + const QString &parentDBusName, + int ppid, + uint instanceNumber) { + return 1; +} + +void GtkIntegration::Start(Type type) { +} + +void GtkIntegration::Autorestart(Type type) { +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.cpp index 3c8aac4b40..5bd9442ef9 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.cpp @@ -9,10 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/linux/linux_gtk_integration_p.h" #include "platform/linux/linux_gdk_helper.h" -#include "window/window_controller.h" -#include "core/application.h" -#include #include namespace Platform { @@ -22,6 +19,8 @@ namespace { using namespace Platform::Gtk; +rpl::event_stream GtkOpenWithDialogResponseStream; + struct GtkWidgetDeleter { void operator()(GtkWidget *widget) { gtk_widget_destroy(widget); @@ -38,22 +37,22 @@ bool Supported() { && (gtk_widget_destroy != nullptr); } -class GtkOpenWithDialog : public QWindow { +class GtkOpenWithDialog { public: - GtkOpenWithDialog(const QString &filepath); - - bool exec(); + GtkOpenWithDialog( + const QString &parent, + const QString &filepath); private: static void handleResponse(GtkOpenWithDialog *dialog, int responseId); const Glib::RefPtr _file; const std::unique_ptr _gtkWidget; - QEventLoop _loop; - std::optional _result; }; -GtkOpenWithDialog::GtkOpenWithDialog(const QString &filepath) +GtkOpenWithDialog::GtkOpenWithDialog( + const QString &parent, + const QString &filepath) : _file(Gio::File::create_for_path(filepath.toStdString())) , _gtkWidget(gtk_app_chooser_dialog_new( nullptr, @@ -64,31 +63,19 @@ GtkOpenWithDialog::GtkOpenWithDialog(const QString &filepath) "response", G_CALLBACK(handleResponse), this); -} -bool GtkOpenWithDialog::exec() { gtk_widget_realize(_gtkWidget.get()); - if (const auto activeWindow = Core::App().activeWindow()) { - Platform::internal::GdkSetTransientFor( - gtk_widget_get_window(_gtkWidget.get()), - activeWindow->widget()->windowHandle()); - } + Platform::internal::GdkSetTransientFor( + gtk_widget_get_window(_gtkWidget.get()), + parent); - QGuiApplicationPrivate::showModalWindow(this); gtk_widget_show(_gtkWidget.get()); - - if (!_result.has_value()) { - _loop.exec(); - } - - QGuiApplicationPrivate::hideModalWindow(this); - return *_result; } void GtkOpenWithDialog::handleResponse(GtkOpenWithDialog *dialog, int responseId) { Glib::RefPtr chosenAppInfo; - dialog->_result = true; + bool result = true; switch (responseId) { case GTK_RESPONSE_OK: @@ -110,21 +97,28 @@ void GtkOpenWithDialog::handleResponse(GtkOpenWithDialog *dialog, int responseId break; default: - dialog->_result = false; + result = false; break; } - dialog->_loop.quit(); + GtkOpenWithDialogResponseStream.fire_copy(result); + delete dialog; } } // namespace -bool ShowGtkOpenWithDialog(const QString &filepath) { +bool ShowGtkOpenWithDialog( + const QString &parent, + const QString &filepath) { if (!Supported()) { return false; } - return GtkOpenWithDialog(filepath).exec(); + return new GtkOpenWithDialog(parent, filepath); +} + +rpl::producer GtkOpenWithDialogResponse() { + return GtkOpenWithDialogResponseStream.events(); } } // namespace internal diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.h b/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.h index 592345a1a3..477af90345 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.h +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.h @@ -11,7 +11,11 @@ namespace Platform { namespace File { namespace internal { -bool ShowGtkOpenWithDialog(const QString &filepath); +bool ShowGtkOpenWithDialog( + const QString &parent, + const QString &filepath); + +[[nodiscard]] rpl::producer GtkOpenWithDialogResponse(); } // namespace internal } // namespace File diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6e5db4dec1..7f13948eee 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -743,11 +743,8 @@ void start() { Glib::set_prgname(cExeName().toStdString()); Glib::set_application_name(std::string(AppName)); - if (const auto integration = BaseGtkIntegration::Instance()) { - integration->prepareEnvironment(); - } else { - g_warning("GTK integration is disabled, some features unavailable."); - } + GtkIntegration::Start(GtkIntegration::Type::Base); + GtkIntegration::Start(GtkIntegration::Type::TDesktop); #ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE g_warning( @@ -944,13 +941,16 @@ bool OpenSystemSettings(SystemSettingsType type) { namespace ThirdParty { void start() { + GtkIntegration::Autorestart(GtkIntegration::Type::Base); + GtkIntegration::Autorestart(GtkIntegration::Type::TDesktop); + if (const auto integration = BaseGtkIntegration::Instance()) { - integration->load(); + integration->load(GtkIntegration::AllowedBackends()); integration->initializeSettings(); } if (const auto integration = GtkIntegration::Instance()) { - integration->load(); + integration->load(GtkIntegration::AllowedBackends()); } // wait for interface announce to know if native window frame is supported