Move GTK integration out of process with D-Bus

This commit is contained in:
Ilya Fedin 2021-06-23 03:20:40 +04:00 committed by John Preston
parent d3c9bb0bc6
commit 551ea7d879
13 changed files with 729 additions and 74 deletions

View File

@ -93,6 +93,9 @@ jobs:
DEFINE="" DEFINE=""
if [ -n "${{ matrix.defines }}" ]; then if [ -n "${{ matrix.defines }}" ]; then
DEFINE="-D ${{ matrix.defines }}=ON" 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 Define from matrix: $DEFINE
echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV
else else

View File

@ -112,6 +112,7 @@ if (LINUX)
endif() endif()
if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION) if (NOT DESKTOP_APP_DISABLE_GTK_INTEGRATION)
target_link_libraries(Telegram PRIVATE rt)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
if (DESKTOP_APP_USE_PACKAGED AND NOT DESKTOP_APP_USE_PACKAGED_LAZY) if (DESKTOP_APP_USE_PACKAGED AND NOT DESKTOP_APP_USE_PACKAGED_LAZY)

View File

@ -19,7 +19,7 @@ public:
static std::unique_ptr<Launcher> Create(int argc, char *argv[]); static std::unique_ptr<Launcher> Create(int argc, char *argv[]);
int exec(); virtual int exec();
QString argumentsString() const; QString argumentsString() const;
bool customWorkingDir() const; bool customWorkingDir() const;

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/crash_reports.h" #include "core/crash_reports.h"
#include "core/update_checker.h" #include "core/update_checker.h"
#include "platform/linux/linux_gtk_integration.h"
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>
@ -23,6 +24,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Platform { namespace Platform {
namespace { namespace {
using Platform::internal::GtkIntegration;
class Arguments { class Arguments {
public: public:
void push(QByteArray argument) { void push(QByteArray argument) {
@ -45,7 +48,32 @@ private:
} // namespace } // namespace
Launcher::Launcher(int argc, char *argv[]) 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() { void Launcher::initHook() {

View File

@ -15,10 +15,14 @@ class Launcher : public Core::Launcher {
public: public:
Launcher(int argc, char *argv[]); Launcher(int argc, char *argv[]);
int exec();
private: private:
void initHook() override; void initHook() override;
bool launchUpdater(UpdaterLaunch action) override; bool launchUpdater(UpdaterLaunch action) override;
std::vector<std::string> _arguments;
}; };
} // namespace Platform } // namespace Platform

View File

@ -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.h"
#include "base/platform/linux/base_linux_gtk_integration_p.h" #include "base/platform/linux/base_linux_gtk_integration_p.h"
#include "platform/linux/linux_gtk_integration_p.h" #include "platform/linux/linux_gtk_integration_p.h"
#include "platform/linux/linux_wayland_integration.h"
#include <QtGui/QWindow>
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
extern "C" { 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 #ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
if (gdk_wayland_window_get_type != nullptr if (gdk_wayland_window_get_type != nullptr
&& gdk_wayland_window_set_transient_for_exported != nullptr && gdk_wayland_window_set_transient_for_exported != nullptr
&& GDK_IS_WAYLAND_WINDOW(window)) { && GDK_IS_WAYLAND_WINDOW(window)
if (const auto integration = WaylandIntegration::Instance()) { && parent.startsWith("wayland:")) {
if (const auto handle = integration->nativeHandle(parent) auto handle = parent.mid(8).toUtf8();
; !handle.isEmpty()) { gdk_wayland_window_set_transient_for_exported(
auto handleUtf8 = handle.toUtf8(); window,
gdk_wayland_window_set_transient_for_exported( handle.data());
window, return;
handleUtf8.data());
return;
}
}
} }
#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION #endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
@ -121,23 +114,33 @@ void GdkSetTransientFor(GdkWindow *window, QWindow *parent) {
&& gdk_x11_display_get_xdisplay != nullptr && gdk_x11_display_get_xdisplay != nullptr
&& gdk_x11_window_get_xid != nullptr && gdk_x11_window_get_xid != nullptr
&& gdk_window_get_display != nullptr && gdk_window_get_display != nullptr
&& GDK_IS_X11_WINDOW(window)) { && GDK_IS_X11_WINDOW(window)
XSetTransientForHint( && parent.startsWith("x11:")) {
gdk_x11_display_get_xdisplay(gdk_window_get_display(window)), auto ok = false;
gdk_x11_window_get_xid(window), const auto winId = parent.mid(4).toInt(&ok, 16);
parent->winId()); if (ok) {
return; 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 #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
if (gdk_x11_drawable_get_xdisplay != nullptr if (gdk_x11_drawable_get_xdisplay != nullptr
&& gdk_x11_drawable_get_xid != nullptr) { && gdk_x11_drawable_get_xid != nullptr
XSetTransientForHint( && parent.startsWith("x11:")) {
gdk_x11_drawable_get_xdisplay(window), auto ok = false;
gdk_x11_drawable_get_xid(window), const auto winId = parent.mid(4).toInt(&ok, 16);
parent->winId()); if (ok) {
return; XSetTransientForHint(
gdk_x11_drawable_get_xdisplay(window),
gdk_x11_drawable_get_xid(window),
winId);
return;
}
} }
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
} }

View File

@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once #pragma once
class QLibrary; class QLibrary;
class QWindow;
extern "C" { extern "C" {
#include <gtk/gtk.h> #include <gtk/gtk.h>
@ -19,7 +18,7 @@ namespace Platform {
namespace internal { namespace internal {
void GdkHelperLoad(QLibrary &lib); void GdkHelperLoad(QLibrary &lib);
void GdkSetTransientFor(GdkWindow *window, QWindow *parent); void GdkSetTransientFor(GdkWindow *window, const QString &parent);
} // namespace internal } // namespace internal
} // namespace Platform } // namespace Platform

View File

@ -7,11 +7,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "platform/linux/linux_gtk_integration.h" #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.h"
#include "base/platform/linux/base_linux_gtk_integration_p.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_gtk_integration_p.h"
#include "platform/linux/linux_gdk_helper.h" #include "platform/linux/linux_gdk_helper.h"
#include "platform/linux/linux_gtk_open_with_dialog.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 <QtCore/QProcess>
#include <private/qguiapplication_p.h>
#include <giomm.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
namespace Platform { namespace Platform {
namespace internal { namespace internal {
@ -21,6 +41,31 @@ using BaseGtkIntegration = base::Platform::GtkIntegration;
namespace { 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(<node>
<interface name='org.telegram.desktop.GtkIntegration'>
<method name='Load'>
<arg type='s' name='allowed-backends' direction='in'/>
</method>
<method name='ShowOpenWithDialog'>
<arg type='s' name='parent' direction='in'/>
<arg type='s' name='filepath' direction='in'/>
</method>
<method name='GetImageFromClipboard'>
<arg type='h' name='shm-descriptor' direction='out'/>
<arg type='i' name='shm-size' direction='out'/>
</method>
<signal name='OpenWithDialogResponse'>
<arg type='b' name='result' direction='out'/>
</signal>
</interface>
</node>)INTROSPECTION"_cs;
bool GetImageFromClipboardSupported() { bool GetImageFromClipboardSupported() {
return (gtk_clipboard_get != nullptr) return (gtk_clipboard_get != nullptr)
&& (gtk_clipboard_wait_for_contents != nullptr) && (gtk_clipboard_wait_for_contents != nullptr)
@ -37,7 +82,182 @@ bool GetImageFromClipboardSupported() {
} // namespace } // 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<Gio::DBus::Connection>();
}
}())
, interfaceVTable(sigc::mem_fun(this, &Private::handleMethodCall))
, serviceName(kService.utf16().arg(getpid()).toStdString()) {
}
void handleMethodCall(
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &method_name,
const Glib::VariantContainerBase &parameters,
const Glib::RefPtr<Gio::DBus::MethodInvocation> &invocation);
const Glib::RefPtr<Gio::DBus::Connection> dbusConnection;
const Gio::DBus::InterfaceVTable interfaceVTable;
Glib::RefPtr<Gio::DBus::NodeInfo> introspectionData;
Glib::ustring serviceName;
Glib::ustring parentDBusName;
bool remoting = true;
uint registerId = 0;
uint parentServiceWatcherId = 0;
};
void GtkIntegration::Private::handleMethodCall(
const Glib::RefPtr<Gio::DBus::Connection> &connection,
const Glib::ustring &sender,
const Glib::ustring &object_path,
const Glib::ustring &interface_name,
const Glib::ustring &method_name,
const Glib::VariantContainerBase &parameters,
const Glib::RefPtr<Gio::DBus::MethodInvocation> &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<uchar> 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<int>::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<Private>()) {
}
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() { GtkIntegration *GtkIntegration::Instance() {
@ -49,10 +269,31 @@ GtkIntegration *GtkIntegration::Instance() {
return &instance; return &instance;
} }
void GtkIntegration::load() { void GtkIntegration::load(const QString &allowedBackends) {
static bool Loaded = false; static bool Loaded = false;
Expects(!Loaded); 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()) { if (!BaseGtkIntegration::Instance()->loaded()) {
return; return;
} }
@ -86,13 +327,233 @@ void GtkIntegration::load() {
Loaded = true; 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 { 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<Gio::DBus::Connection> &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<bool>(
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 GtkIntegration::getImageFromClipboard() const {
QImage data; QImage data;
if (_private->remoting) {
if (!_private->dbusConnection) {
return data;
}
try {
Glib::RefPtr<Gio::UnixFDList> outFdList;
auto reply = _private->dbusConnection->call_sync(
std::string(kObjectPath),
std::string(kInterface),
"GetImageFromClipboard",
{},
{},
outFdList,
_private->serviceName);
const auto streamSize = base::Platform::GlibVariantCast<int>(
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<uchar> 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()) { if (!GetImageFromClipboardSupported()) {
return data; return data;
} }
@ -130,5 +591,113 @@ QImage GtkIntegration::getImageFromClipboard() const {
return data; 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 internal
} // namespace Platform } // namespace Platform

View File

@ -12,16 +12,39 @@ namespace internal {
class GtkIntegration { class GtkIntegration {
public: public:
enum class Type {
Base,
Webview,
TDesktop,
};
static GtkIntegration *Instance(); 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]] bool showOpenWithDialog(const QString &filepath) const;
[[nodiscard]] QImage getImageFromClipboard() 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: private:
GtkIntegration(); GtkIntegration();
~GtkIntegration();
class Private;
const std::unique_ptr<Private> _private;
}; };
} // namespace internal } // namespace internal

View File

@ -10,14 +10,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Platform { namespace Platform {
namespace internal { namespace internal {
class GtkIntegration::Private {
};
GtkIntegration::GtkIntegration() { GtkIntegration::GtkIntegration() {
} }
GtkIntegration::~GtkIntegration() = default;
GtkIntegration *GtkIntegration::Instance() { GtkIntegration *GtkIntegration::Instance() {
return nullptr; 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 { bool GtkIntegration::showOpenWithDialog(const QString &filepath) const {
@ -28,5 +37,23 @@ QImage GtkIntegration::getImageFromClipboard() const {
return {}; 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 internal
} // namespace Platform } // namespace Platform

View File

@ -9,10 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/linux/linux_gtk_integration_p.h" #include "platform/linux/linux_gtk_integration_p.h"
#include "platform/linux/linux_gdk_helper.h" #include "platform/linux/linux_gdk_helper.h"
#include "window/window_controller.h"
#include "core/application.h"
#include <private/qguiapplication_p.h>
#include <giomm.h> #include <giomm.h>
namespace Platform { namespace Platform {
@ -22,6 +19,8 @@ namespace {
using namespace Platform::Gtk; using namespace Platform::Gtk;
rpl::event_stream<bool> GtkOpenWithDialogResponseStream;
struct GtkWidgetDeleter { struct GtkWidgetDeleter {
void operator()(GtkWidget *widget) { void operator()(GtkWidget *widget) {
gtk_widget_destroy(widget); gtk_widget_destroy(widget);
@ -38,22 +37,22 @@ bool Supported() {
&& (gtk_widget_destroy != nullptr); && (gtk_widget_destroy != nullptr);
} }
class GtkOpenWithDialog : public QWindow { class GtkOpenWithDialog {
public: public:
GtkOpenWithDialog(const QString &filepath); GtkOpenWithDialog(
const QString &parent,
bool exec(); const QString &filepath);
private: private:
static void handleResponse(GtkOpenWithDialog *dialog, int responseId); static void handleResponse(GtkOpenWithDialog *dialog, int responseId);
const Glib::RefPtr<Gio::File> _file; const Glib::RefPtr<Gio::File> _file;
const std::unique_ptr<GtkWidget, GtkWidgetDeleter> _gtkWidget; const std::unique_ptr<GtkWidget, GtkWidgetDeleter> _gtkWidget;
QEventLoop _loop;
std::optional<bool> _result;
}; };
GtkOpenWithDialog::GtkOpenWithDialog(const QString &filepath) GtkOpenWithDialog::GtkOpenWithDialog(
const QString &parent,
const QString &filepath)
: _file(Gio::File::create_for_path(filepath.toStdString())) : _file(Gio::File::create_for_path(filepath.toStdString()))
, _gtkWidget(gtk_app_chooser_dialog_new( , _gtkWidget(gtk_app_chooser_dialog_new(
nullptr, nullptr,
@ -64,31 +63,19 @@ GtkOpenWithDialog::GtkOpenWithDialog(const QString &filepath)
"response", "response",
G_CALLBACK(handleResponse), G_CALLBACK(handleResponse),
this); this);
}
bool GtkOpenWithDialog::exec() {
gtk_widget_realize(_gtkWidget.get()); gtk_widget_realize(_gtkWidget.get());
if (const auto activeWindow = Core::App().activeWindow()) { Platform::internal::GdkSetTransientFor(
Platform::internal::GdkSetTransientFor( gtk_widget_get_window(_gtkWidget.get()),
gtk_widget_get_window(_gtkWidget.get()), parent);
activeWindow->widget()->windowHandle());
}
QGuiApplicationPrivate::showModalWindow(this);
gtk_widget_show(_gtkWidget.get()); gtk_widget_show(_gtkWidget.get());
if (!_result.has_value()) {
_loop.exec();
}
QGuiApplicationPrivate::hideModalWindow(this);
return *_result;
} }
void GtkOpenWithDialog::handleResponse(GtkOpenWithDialog *dialog, int responseId) { void GtkOpenWithDialog::handleResponse(GtkOpenWithDialog *dialog, int responseId) {
Glib::RefPtr<Gio::AppInfo> chosenAppInfo; Glib::RefPtr<Gio::AppInfo> chosenAppInfo;
dialog->_result = true; bool result = true;
switch (responseId) { switch (responseId) {
case GTK_RESPONSE_OK: case GTK_RESPONSE_OK:
@ -110,21 +97,28 @@ void GtkOpenWithDialog::handleResponse(GtkOpenWithDialog *dialog, int responseId
break; break;
default: default:
dialog->_result = false; result = false;
break; break;
} }
dialog->_loop.quit(); GtkOpenWithDialogResponseStream.fire_copy(result);
delete dialog;
} }
} // namespace } // namespace
bool ShowGtkOpenWithDialog(const QString &filepath) { bool ShowGtkOpenWithDialog(
const QString &parent,
const QString &filepath) {
if (!Supported()) { if (!Supported()) {
return false; return false;
} }
return GtkOpenWithDialog(filepath).exec(); return new GtkOpenWithDialog(parent, filepath);
}
rpl::producer<bool> GtkOpenWithDialogResponse() {
return GtkOpenWithDialogResponseStream.events();
} }
} // namespace internal } // namespace internal

View File

@ -11,7 +11,11 @@ namespace Platform {
namespace File { namespace File {
namespace internal { namespace internal {
bool ShowGtkOpenWithDialog(const QString &filepath); bool ShowGtkOpenWithDialog(
const QString &parent,
const QString &filepath);
[[nodiscard]] rpl::producer<bool> GtkOpenWithDialogResponse();
} // namespace internal } // namespace internal
} // namespace File } // namespace File

View File

@ -743,11 +743,8 @@ void start() {
Glib::set_prgname(cExeName().toStdString()); Glib::set_prgname(cExeName().toStdString());
Glib::set_application_name(std::string(AppName)); Glib::set_application_name(std::string(AppName));
if (const auto integration = BaseGtkIntegration::Instance()) { GtkIntegration::Start(GtkIntegration::Type::Base);
integration->prepareEnvironment(); GtkIntegration::Start(GtkIntegration::Type::TDesktop);
} else {
g_warning("GTK integration is disabled, some features unavailable.");
}
#ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE #ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE
g_warning( g_warning(
@ -944,13 +941,16 @@ bool OpenSystemSettings(SystemSettingsType type) {
namespace ThirdParty { namespace ThirdParty {
void start() { void start() {
GtkIntegration::Autorestart(GtkIntegration::Type::Base);
GtkIntegration::Autorestart(GtkIntegration::Type::TDesktop);
if (const auto integration = BaseGtkIntegration::Instance()) { if (const auto integration = BaseGtkIntegration::Instance()) {
integration->load(); integration->load(GtkIntegration::AllowedBackends());
integration->initializeSettings(); integration->initializeSettings();
} }
if (const auto integration = GtkIntegration::Instance()) { if (const auto integration = GtkIntegration::Instance()) {
integration->load(); integration->load(GtkIntegration::AllowedBackends());
} }
// wait for interface announce to know if native window frame is supported // wait for interface announce to know if native window frame is supported