mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-29 06:42:42 +00:00
The Qt documentation says: This is the file name, without the full path or the trailing ".desktop" extension of the desktop entry that represents this application according to the freedesktop desktop entry specification. Qt 6.5.2 also automatically fixes it breaking all the current tdesktop and desktop-app usage expecting the file extension.
321 lines
7.7 KiB
C++
321 lines
7.7 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "platform/linux/integration_linux.h"
|
|
|
|
#include "platform/platform_integration.h"
|
|
#include "base/platform/base_platform_info.h"
|
|
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
|
#include "base/platform/linux/base_linux_xdp_utilities.h"
|
|
#include "core/sandbox.h"
|
|
#include "core/application.h"
|
|
#include "core/core_settings.h"
|
|
#include "base/random.h"
|
|
|
|
#include <QtCore/QAbstractEventDispatcher>
|
|
|
|
#include <xdpinhibit/xdpinhibit.hpp>
|
|
#include <giomm.h>
|
|
|
|
typedef GApplication TDesktopApplication;
|
|
typedef GApplicationClass TDesktopApplicationClass;
|
|
|
|
G_DEFINE_TYPE(
|
|
TDesktopApplication,
|
|
t_desktop_application,
|
|
G_TYPE_APPLICATION)
|
|
|
|
static void t_desktop_application_class_init(
|
|
TDesktopApplicationClass *klass) {
|
|
const auto application_class = G_APPLICATION_CLASS(klass);
|
|
|
|
application_class->local_command_line = [](
|
|
GApplication *application,
|
|
char ***arguments,
|
|
int *exit_status) -> gboolean {
|
|
return false;
|
|
};
|
|
|
|
application_class->command_line = [](
|
|
GApplication *application,
|
|
GApplicationCommandLine *cmdline) {
|
|
return 0;
|
|
};
|
|
|
|
application_class->before_emit = [](
|
|
GApplication *application,
|
|
GVariant *platformData) {
|
|
if (Platform::IsWayland()) {
|
|
static const auto keys = {
|
|
"activation-token",
|
|
"desktop-startup-id",
|
|
};
|
|
for (const auto &key : keys) {
|
|
const char *token = nullptr;
|
|
g_variant_lookup(platformData, key, "&s", &token);
|
|
if (token) {
|
|
qputenv("XDG_ACTIVATION_TOKEN", token);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
application_class->add_platform_data = [](
|
|
GApplication *application,
|
|
GVariantBuilder *builder) {
|
|
if (Platform::IsWayland()) {
|
|
const auto token = qgetenv("XDG_ACTIVATION_TOKEN");
|
|
if (!token.isEmpty()) {
|
|
g_variant_builder_add(
|
|
builder,
|
|
"{sv}",
|
|
"activation-token",
|
|
g_variant_new_string(token.constData()));
|
|
qunsetenv("XDG_ACTIVATION_TOKEN");
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
static void t_desktop_application_init(TDesktopApplication *application) {
|
|
}
|
|
|
|
namespace Platform {
|
|
namespace {
|
|
|
|
using namespace gi::repository;
|
|
namespace Gio = gi::repository::Gio;
|
|
|
|
class LinuxIntegration final : public Integration {
|
|
public:
|
|
LinuxIntegration();
|
|
|
|
void init() override;
|
|
|
|
private:
|
|
[[nodiscard]] XdpInhibit::Inhibit inhibit() {
|
|
return _inhibitProxy;
|
|
}
|
|
|
|
void initInhibit();
|
|
|
|
static void LaunchNativeApplication();
|
|
|
|
XdpInhibit::InhibitProxy _inhibitProxy;
|
|
base::Platform::XDP::SettingWatcher _darkModeWatcher;
|
|
};
|
|
|
|
LinuxIntegration::LinuxIntegration()
|
|
: _inhibitProxy(
|
|
XdpInhibit::InhibitProxy::new_for_bus_sync(
|
|
Gio::BusType::SESSION_,
|
|
Gio::DBusProxyFlags::DO_NOT_AUTO_START_AT_CONSTRUCTION_,
|
|
std::string(base::Platform::XDP::kService),
|
|
std::string(base::Platform::XDP::kObjectPath),
|
|
nullptr))
|
|
, _darkModeWatcher([](
|
|
const Glib::ustring &group,
|
|
const Glib::ustring &key,
|
|
const Glib::VariantBase &value) {
|
|
if (group == "org.freedesktop.appearance"
|
|
&& key == "color-scheme") {
|
|
try {
|
|
const auto ivalue = base::Platform::GlibVariantCast<uint>(value);
|
|
|
|
crl::on_main([=] {
|
|
Core::App().settings().setSystemDarkMode(ivalue == 1);
|
|
});
|
|
} catch (...) {
|
|
}
|
|
}
|
|
}) {
|
|
LOG(("Icon theme: %1").arg(QIcon::themeName()));
|
|
LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName()));
|
|
|
|
if (!QCoreApplication::eventDispatcher()->inherits(
|
|
"QEventDispatcherGlib")) {
|
|
g_warning("Qt is running without GLib event loop integration, "
|
|
"except various functionality to not to work.");
|
|
}
|
|
}
|
|
|
|
void LinuxIntegration::init() {
|
|
initInhibit();
|
|
|
|
Glib::signal_idle().connect_once([] {
|
|
LaunchNativeApplication();
|
|
});
|
|
}
|
|
|
|
void LinuxIntegration::initInhibit() {
|
|
if (!_inhibitProxy) {
|
|
return;
|
|
}
|
|
|
|
auto uniqueName = _inhibitProxy.get_connection().get_unique_name();
|
|
uniqueName.erase(0, 1);
|
|
uniqueName.replace(uniqueName.find('.'), 1, 1, '_');
|
|
|
|
const auto handleToken = "tdesktop"
|
|
+ std::to_string(base::RandomValue<uint>());
|
|
|
|
const auto sessionHandleToken = "tdesktop"
|
|
+ std::to_string(base::RandomValue<uint>());
|
|
|
|
const auto sessionHandle = "/org/freedesktop/portal/desktop/session/"
|
|
+ uniqueName
|
|
+ '/'
|
|
+ sessionHandleToken;
|
|
|
|
inhibit().signal_state_changed().connect([
|
|
mySessionHandle = sessionHandle
|
|
](
|
|
XdpInhibit::Inhibit,
|
|
const std::string &sessionHandle,
|
|
GLib::Variant state) {
|
|
if (sessionHandle != mySessionHandle) {
|
|
return;
|
|
}
|
|
|
|
Core::App().setScreenIsLocked(
|
|
GLib::VariantDict::new_(
|
|
state
|
|
).lookup_value(
|
|
"screensaver-active"
|
|
).get_boolean()
|
|
);
|
|
});
|
|
|
|
const auto options = std::array{
|
|
GLib::Variant::new_dict_entry(
|
|
GLib::Variant::new_string("handle_token"),
|
|
GLib::Variant::new_variant(
|
|
GLib::Variant::new_string(handleToken))),
|
|
GLib::Variant::new_dict_entry(
|
|
GLib::Variant::new_string("session_handle_token"),
|
|
GLib::Variant::new_variant(
|
|
GLib::Variant::new_string(sessionHandleToken))),
|
|
};
|
|
|
|
inhibit().call_create_monitor(
|
|
{},
|
|
GLib::Variant::new_array(options.data(), options.size()),
|
|
nullptr);
|
|
}
|
|
|
|
void LinuxIntegration::LaunchNativeApplication() {
|
|
const auto appId = QGuiApplication::desktopFileName().toStdString();
|
|
|
|
const auto app = Glib::wrap(
|
|
G_APPLICATION(
|
|
g_object_new(
|
|
t_desktop_application_get_type(),
|
|
"application-id",
|
|
::Gio::Application::id_is_valid(appId)
|
|
? appId.c_str()
|
|
: nullptr,
|
|
"flags",
|
|
G_APPLICATION_HANDLES_OPEN,
|
|
nullptr)));
|
|
|
|
app->signal_startup().connect([=] {
|
|
// GNotification
|
|
InvokeQueued(qApp, [] {
|
|
Core::App().notifications().createManager();
|
|
});
|
|
|
|
QEventLoop().exec();
|
|
app->quit();
|
|
}, true);
|
|
|
|
app->signal_activate().connect([] {
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
|
Core::App().activate();
|
|
});
|
|
}, true);
|
|
|
|
app->signal_open().connect([](
|
|
const ::Gio::Application::type_vec_files &files,
|
|
const Glib::ustring &hint) {
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
|
for (const auto &file : files) {
|
|
QFileOpenEvent e(
|
|
QUrl(QString::fromStdString(file->get_uri())));
|
|
QGuiApplication::sendEvent(qApp, &e);
|
|
}
|
|
});
|
|
}, true);
|
|
|
|
app->add_action("quit", [] {
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
|
Core::Quit();
|
|
});
|
|
});
|
|
|
|
using Window::Notifications::Manager;
|
|
using NotificationId = Manager::NotificationId;
|
|
using NotificationIdTuple = std::invoke_result_t<
|
|
decltype(&NotificationId::toTuple),
|
|
NotificationId*
|
|
>;
|
|
|
|
const auto notificationIdVariantType = [] {
|
|
try {
|
|
return base::Platform::MakeGlibVariant(
|
|
NotificationId().toTuple()).get_type();
|
|
} catch (...) {
|
|
return Glib::VariantType();
|
|
}
|
|
}();
|
|
|
|
app->add_action_with_parameter(
|
|
"notification-activate",
|
|
notificationIdVariantType,
|
|
[](const Glib::VariantBase ¶meter) {
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
|
try {
|
|
const auto &app = Core::App();
|
|
app.notifications().manager().notificationActivated(
|
|
NotificationId::FromTuple(
|
|
base::Platform::GlibVariantCast<
|
|
NotificationIdTuple
|
|
>(parameter)));
|
|
} catch (...) {
|
|
}
|
|
});
|
|
});
|
|
|
|
app->add_action_with_parameter(
|
|
"notification-mark-as-read",
|
|
notificationIdVariantType,
|
|
[](const Glib::VariantBase ¶meter) {
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
|
try {
|
|
const auto &app = Core::App();
|
|
app.notifications().manager().notificationReplied(
|
|
NotificationId::FromTuple(
|
|
base::Platform::GlibVariantCast<
|
|
NotificationIdTuple
|
|
>(parameter)),
|
|
{});
|
|
} catch (...) {
|
|
}
|
|
});
|
|
});
|
|
|
|
app->run(0, nullptr);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<Integration> CreateIntegration() {
|
|
return std::make_unique<LinuxIntegration>();
|
|
}
|
|
|
|
} // namespace Platform
|