mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-05-02 22:13:57 +02:00
It's used if there's a gtk notification daemon or application is running sandboxed without access to the freedesktop protocol. GNotification API is poor, but should feel native on environments using GNOME technologies.
899 lines
22 KiB
C++
899 lines
22 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/specific_linux.h"
|
|
|
|
#include "base/random.h"
|
|
#include "base/options.h"
|
|
#include "base/platform/base_platform_info.h"
|
|
#include "platform/linux/linux_desktop_environment.h"
|
|
#include "platform/linux/linux_wayland_integration.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "mainwindow.h"
|
|
#include "storage/localstorage.h"
|
|
#include "core/sandbox.h"
|
|
#include "core/application.h"
|
|
#include "core/local_url_handlers.h"
|
|
#include "core/core_settings.h"
|
|
#include "core/update_checker.h"
|
|
#include "window/window_controller.h"
|
|
#include "webview/platform/linux/webview_linux_webkit2gtk.h"
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
#include "base/platform/linux/base_linux_glibmm_helper.h"
|
|
#include "base/platform/linux/base_linux_dbus_utilities.h"
|
|
#include "base/platform/linux/base_linux_xdp_utilities.h"
|
|
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
#include "base/platform/linux/base_linux_xcb_utilities.h"
|
|
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
|
|
#include <QtWidgets/QApplication>
|
|
#include <QtWidgets/QSystemTrayIcon>
|
|
#include <QtCore/QStandardPaths>
|
|
#include <QtCore/QProcess>
|
|
|
|
#include <kshell.h>
|
|
#include <ksandbox.h>
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
#include <glibmm.h>
|
|
#include <giomm.h>
|
|
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <cstdlib>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <pwd.h>
|
|
|
|
#include <iostream>
|
|
|
|
using namespace Platform;
|
|
using Platform::internal::WaylandIntegration;
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
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->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()));
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
static void t_desktop_application_init(TDesktopApplication *application) {
|
|
}
|
|
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
|
|
namespace Platform {
|
|
namespace {
|
|
|
|
constexpr auto kDesktopFile = ":/misc/telegramdesktop.desktop"_cs;
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
void PortalAutostart(bool start, bool silent) {
|
|
if (cExeName().isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const auto connection = Gio::DBus::Connection::get_sync(
|
|
Gio::DBus::BusType::SESSION);
|
|
|
|
const auto parentWindowId = [&]() -> Glib::ustring {
|
|
const auto activeWindow = Core::App().activeWindow();
|
|
if (!activeWindow) {
|
|
return {};
|
|
}
|
|
|
|
return base::Platform::XDP::ParentWindowID(
|
|
activeWindow->widget()->windowHandle());
|
|
}();
|
|
|
|
const auto handleToken = Glib::ustring("tdesktop")
|
|
+ std::to_string(base::RandomValue<uint>());
|
|
|
|
std::map<Glib::ustring, Glib::VariantBase> options;
|
|
options["handle_token"] = Glib::Variant<Glib::ustring>::create(
|
|
handleToken);
|
|
options["reason"] = Glib::Variant<Glib::ustring>::create(
|
|
tr::lng_settings_auto_start(tr::now).toStdString());
|
|
options["autostart"] = Glib::Variant<bool>::create(start);
|
|
options["commandline"] = Glib::Variant<std::vector<
|
|
Glib::ustring
|
|
>>::create({
|
|
cExeName().toStdString(),
|
|
"-workdir",
|
|
cWorkingDir().toStdString(),
|
|
"-autostart",
|
|
});
|
|
options["dbus-activatable"] = Glib::Variant<bool>::create(false);
|
|
|
|
auto uniqueName = connection->get_unique_name();
|
|
uniqueName.erase(0, 1);
|
|
uniqueName.replace(uniqueName.find('.'), 1, 1, '_');
|
|
|
|
const auto requestPath = Glib::ustring(
|
|
"/org/freedesktop/portal/desktop/request/")
|
|
+ uniqueName
|
|
+ '/'
|
|
+ handleToken;
|
|
|
|
const auto loop = Glib::MainLoop::create();
|
|
|
|
const auto signalId = connection->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 {
|
|
const auto response = base::Platform::GlibVariantCast<
|
|
uint>(parameters.get_child(0));
|
|
|
|
if (response && !silent) {
|
|
LOG(("Portal Autostart Error: Request denied"));
|
|
}
|
|
} catch (const std::exception &e) {
|
|
if (!silent) {
|
|
LOG(("Portal Autostart Error: %1").arg(
|
|
QString::fromStdString(e.what())));
|
|
}
|
|
}
|
|
|
|
loop->quit();
|
|
},
|
|
std::string(base::Platform::XDP::kService),
|
|
"org.freedesktop.portal.Request",
|
|
"Response",
|
|
requestPath);
|
|
|
|
const auto signalGuard = gsl::finally([&] {
|
|
if (signalId != 0) {
|
|
connection->signal_unsubscribe(signalId);
|
|
}
|
|
});
|
|
|
|
connection->call_sync(
|
|
std::string(base::Platform::XDP::kObjectPath),
|
|
"org.freedesktop.portal.Background",
|
|
"RequestBackground",
|
|
base::Platform::MakeGlibVariant(std::tuple{
|
|
parentWindowId,
|
|
options,
|
|
}),
|
|
std::string(base::Platform::XDP::kService));
|
|
|
|
if (signalId != 0) {
|
|
QWidget window;
|
|
window.setAttribute(Qt::WA_DontShowOnScreen);
|
|
window.setWindowModality(Qt::ApplicationModal);
|
|
window.show();
|
|
loop->run();
|
|
}
|
|
} catch (const std::exception &e) {
|
|
if (!silent) {
|
|
LOG(("Portal Autostart Error: %1").arg(
|
|
QString::fromStdString(e.what())));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LaunchGApplication() {
|
|
const auto connection = [] {
|
|
try {
|
|
return Gio::DBus::Connection::get_sync(
|
|
Gio::DBus::BusType::SESSION);
|
|
} catch (...) {
|
|
return Glib::RefPtr<Gio::DBus::Connection>();
|
|
}
|
|
}();
|
|
|
|
using namespace base::Platform::DBus;
|
|
const auto activatableNames = [&] {
|
|
try {
|
|
if (connection) {
|
|
return ListActivatableNames(connection);
|
|
}
|
|
} catch (...) {
|
|
}
|
|
|
|
return std::vector<Glib::ustring>();
|
|
}();
|
|
|
|
const auto freedesktopNotifications = [&] {
|
|
try {
|
|
if (connection && NameHasOwner(
|
|
connection,
|
|
"org.freedesktop.Notifications")) {
|
|
return true;
|
|
}
|
|
} catch (...) {
|
|
}
|
|
|
|
if (ranges::contains(
|
|
activatableNames,
|
|
"org.freedesktop.Notifications")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
const auto gtkNotifications = [&] {
|
|
try {
|
|
if (connection && NameHasOwner(
|
|
connection,
|
|
"org.gtk.Notifications")) {
|
|
return true;
|
|
}
|
|
} catch (...) {
|
|
}
|
|
|
|
if (ranges::contains(activatableNames, "org.gtk.Notifications")) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
if (OptionGApplication.value()
|
|
|| gtkNotifications()
|
|
|| (KSandbox::isFlatpak() && !freedesktopNotifications())) {
|
|
Glib::signal_idle().connect_once([] {
|
|
const auto appId = QGuiApplication::desktopFileName()
|
|
.chopped(8)
|
|
.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([=] {
|
|
QEventLoop loop;
|
|
loop.exec(QEventLoop::ApplicationExec);
|
|
app->quit();
|
|
}, true);
|
|
|
|
app->signal_activate().connect([] {
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
|
if (const auto w = App::wnd()) {
|
|
w->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) {
|
|
if (file->get_uri_scheme() == "file") {
|
|
gSendPaths.append(
|
|
QString::fromStdString(file->get_path()));
|
|
continue;
|
|
}
|
|
const auto url = QString::fromStdString(
|
|
file->get_uri());
|
|
if (url.isEmpty()) {
|
|
continue;
|
|
}
|
|
if (url.startsWith(qstr("interpret://"))) {
|
|
gSendPaths.append(url);
|
|
continue;
|
|
}
|
|
if (Core::StartUrlRequiresActivate(url)) {
|
|
if (const auto w = App::wnd()) {
|
|
w->activate();
|
|
}
|
|
}
|
|
cSetStartUrl(url);
|
|
Core::App().checkStartUrl();
|
|
}
|
|
if (!cSendPaths().isEmpty()) {
|
|
if (const auto w = App::wnd()) {
|
|
w->sendPaths();
|
|
}
|
|
}
|
|
});
|
|
}, true);
|
|
|
|
app->add_action("Quit", [] {
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([] {
|
|
Core::Quit();
|
|
});
|
|
});
|
|
|
|
using Window::Notifications::Manager;
|
|
using NotificationId = Manager::NotificationId;
|
|
using NotificationIdTuple = std::result_of<
|
|
decltype(&NotificationId::toTuple)(NotificationId*)
|
|
>::type;
|
|
|
|
const auto notificationIdVariantType = [] {
|
|
try {
|
|
return base::Platform::MakeGlibVariant(
|
|
NotificationId().toTuple()).get_type();
|
|
} catch (...) {
|
|
return Glib::VariantType();
|
|
}
|
|
}();
|
|
|
|
app->add_action_with_parameter(
|
|
"notification-reply",
|
|
notificationIdVariantType,
|
|
[](const Glib::VariantBase ¶meter) {
|
|
Core::Sandbox::Instance().customEnterFromEventLoop([&] {
|
|
try {
|
|
const auto &app = Core::App();
|
|
const auto ¬ifications = app.notifications();
|
|
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();
|
|
const auto ¬ifications = app.notifications();
|
|
notifications.manager().notificationReplied(
|
|
NotificationId::FromTuple(
|
|
base::Platform::GlibVariantCast<
|
|
NotificationIdTuple
|
|
>(parameter)),
|
|
{});
|
|
} catch (...) {
|
|
}
|
|
});
|
|
});
|
|
|
|
app->hold();
|
|
app->run(0, nullptr);
|
|
});
|
|
}
|
|
}
|
|
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
|
|
bool GenerateDesktopFile(
|
|
const QString &targetPath,
|
|
const QString &args,
|
|
bool silent = false) {
|
|
if (targetPath.isEmpty() || cExeName().isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
DEBUG_LOG(("App Info: placing .desktop file to %1").arg(targetPath));
|
|
if (!QDir(targetPath).exists()) QDir().mkpath(targetPath);
|
|
|
|
const auto sourceFile = kDesktopFile.utf16();
|
|
const auto targetFile = targetPath + QGuiApplication::desktopFileName();
|
|
|
|
QString fileText;
|
|
|
|
QFile source(sourceFile);
|
|
if (source.open(QIODevice::ReadOnly)) {
|
|
QTextStream s(&source);
|
|
fileText = s.readAll();
|
|
source.close();
|
|
} else {
|
|
if (!silent) {
|
|
LOG(("App Error: Could not open '%1' for read").arg(sourceFile));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QFile target(targetFile);
|
|
if (target.open(QIODevice::WriteOnly)) {
|
|
fileText = fileText.replace(
|
|
QRegularExpression(
|
|
qsl("^TryExec=.*$"),
|
|
QRegularExpression::MultilineOption),
|
|
qsl("TryExec=%1").arg(
|
|
QString(cExeDir() + cExeName()).replace('\\', "\\\\")));
|
|
|
|
fileText = fileText.replace(
|
|
QRegularExpression(
|
|
qsl("^Exec=telegram-desktop(.*)$"),
|
|
QRegularExpression::MultilineOption),
|
|
qsl("Exec=%1\\1").arg(
|
|
KShell::joinArgs({
|
|
cExeDir() + cExeName(),
|
|
"-workdir",
|
|
cWorkingDir(),
|
|
}).replace('\\', "\\\\")));
|
|
|
|
fileText = fileText.replace(
|
|
QRegularExpression(
|
|
qsl("^Exec=(.*) -- %u$"),
|
|
QRegularExpression::MultilineOption),
|
|
qsl("Exec=\\1%1").arg(
|
|
args.isEmpty() ? QString() : ' ' + args));
|
|
|
|
target.write(fileText.toUtf8());
|
|
target.close();
|
|
|
|
if (!Core::UpdaterDisabled()) {
|
|
DEBUG_LOG(("App Info: removing old .desktop files"));
|
|
QFile::remove(qsl("%1telegram.desktop").arg(targetPath));
|
|
QFile::remove(qsl("%1telegramdesktop.desktop").arg(targetPath));
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
if (!silent) {
|
|
LOG(("App Error: Could not open '%1' for write").arg(targetFile));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void SetApplicationIcon(const QIcon &icon) {
|
|
QApplication::setWindowIcon(icon);
|
|
}
|
|
|
|
QString SingleInstanceLocalServerName(const QString &hash) {
|
|
return QDir::tempPath() + '/' + hash + '-' + cGUIDStr();
|
|
}
|
|
|
|
std::optional<bool> IsDarkMode() {
|
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
[[maybe_unused]] static const auto Inited = [] {
|
|
using XDPSettingWatcher = base::Platform::XDP::SettingWatcher;
|
|
static const XDPSettingWatcher Watcher(
|
|
[=](
|
|
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 (...) {
|
|
}
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}();
|
|
|
|
try {
|
|
const auto result = base::Platform::XDP::ReadSetting(
|
|
"org.freedesktop.appearance",
|
|
"color-scheme");
|
|
|
|
if (result.has_value()) {
|
|
const auto value = base::Platform::GlibVariantCast<uint>(*result);
|
|
return value == 1;
|
|
}
|
|
} catch (...) {
|
|
}
|
|
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool AutostartSupported() {
|
|
// snap sandbox doesn't allow creating files
|
|
// in folders with names started with a dot
|
|
// and doesn't provide any api to add an app to autostart
|
|
// thus, autostart isn't supported in snap
|
|
return !KSandbox::isSnap();
|
|
}
|
|
|
|
void AutostartToggle(bool enabled, Fn<void(bool)> done) {
|
|
const auto guard = gsl::finally([&] {
|
|
if (done) {
|
|
done(enabled);
|
|
}
|
|
});
|
|
|
|
const auto silent = !done;
|
|
if (KSandbox::isFlatpak()) {
|
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
PortalAutostart(enabled, silent);
|
|
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
} else {
|
|
const auto autostart = QStandardPaths::writableLocation(
|
|
QStandardPaths::GenericConfigLocation)
|
|
+ qsl("/autostart/");
|
|
|
|
if (enabled) {
|
|
GenerateDesktopFile(autostart, qsl("-autostart"), silent);
|
|
} else {
|
|
QFile::remove(autostart + QGuiApplication::desktopFileName());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AutostartSkip() {
|
|
return !cAutoStart();
|
|
}
|
|
|
|
bool TrayIconSupported() {
|
|
return QSystemTrayIcon::isSystemTrayAvailable();
|
|
}
|
|
|
|
bool SkipTaskbarSupported() {
|
|
if (const auto integration = WaylandIntegration::Instance()) {
|
|
return integration->skipTaskbarSupported();
|
|
}
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
if (IsX11()) {
|
|
return base::Platform::XCB::IsSupportedByWM(
|
|
base::Platform::XCB::GetConnectionFromQt(),
|
|
"_NET_WM_STATE_SKIP_TASKBAR");
|
|
}
|
|
#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace Platform
|
|
|
|
void psActivateProcess(uint64 pid) {
|
|
// objc_activateProgram();
|
|
}
|
|
|
|
QString psAppDataPath() {
|
|
// Previously we used ~/.TelegramDesktop, so look there first.
|
|
// If we find data there, we should still use it.
|
|
auto home = QDir::homePath();
|
|
if (!home.isEmpty()) {
|
|
auto oldPath = home + qsl("/.TelegramDesktop/");
|
|
auto oldSettingsBase = oldPath + qsl("tdata/settings");
|
|
if (QFile::exists(oldSettingsBase + '0')
|
|
|| QFile::exists(oldSettingsBase + '1')
|
|
|| QFile::exists(oldSettingsBase + 's')) {
|
|
return oldPath;
|
|
}
|
|
}
|
|
|
|
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + '/';
|
|
}
|
|
|
|
void psDoCleanup() {
|
|
try {
|
|
Platform::AutostartToggle(false);
|
|
psSendToMenu(false, true);
|
|
} catch (...) {
|
|
}
|
|
}
|
|
|
|
int psCleanup() {
|
|
psDoCleanup();
|
|
return 0;
|
|
}
|
|
|
|
void psDoFixPrevious() {
|
|
}
|
|
|
|
int psFixPrevious() {
|
|
psDoFixPrevious();
|
|
return 0;
|
|
}
|
|
|
|
namespace Platform {
|
|
|
|
void start() {
|
|
QGuiApplication::setDesktopFileName([] {
|
|
if (KSandbox::isFlatpak()) {
|
|
return qEnvironmentVariable("FLATPAK_ID") + qsl(".desktop");
|
|
}
|
|
|
|
if (KSandbox::isSnap()) {
|
|
return qEnvironmentVariable("SNAP_INSTANCE_NAME")
|
|
+ '_'
|
|
+ cExeName()
|
|
+ qsl(".desktop");
|
|
}
|
|
|
|
if (!Core::UpdaterDisabled() && !cExeName().isEmpty()) {
|
|
const auto appimagePath = qsl("file://%1%2").arg(
|
|
cExeDir(),
|
|
cExeName()).toUtf8();
|
|
|
|
char md5Hash[33] = { 0 };
|
|
hashMd5Hex(
|
|
appimagePath.constData(),
|
|
appimagePath.size(),
|
|
md5Hash);
|
|
|
|
return qsl("appimagekit_%1-%2.desktop").arg(
|
|
md5Hash,
|
|
AppName.utf16().replace(' ', '_'));
|
|
}
|
|
|
|
return qsl("telegramdesktop.desktop");
|
|
}());
|
|
|
|
LOG(("Launcher filename: %1").arg(QGuiApplication::desktopFileName()));
|
|
|
|
qputenv("PULSE_PROP_application.name", AppName.utf8());
|
|
qputenv("PULSE_PROP_application.icon_name", base::IconName().toLatin1());
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
Glib::init();
|
|
Gio::init();
|
|
|
|
Glib::set_prgname(cExeName().toStdString());
|
|
Glib::set_application_name(AppName.data());
|
|
|
|
#ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE
|
|
g_warning(
|
|
"Application has been built with foreign rlottie, "
|
|
"animated emojis won't be colored to the selected pack.");
|
|
#endif // DESKTOP_APP_USE_PACKAGED_RLOTTIE
|
|
|
|
#ifdef DESKTOP_APP_USE_PACKAGED_FONTS
|
|
g_warning(
|
|
"Application was built without embedded fonts, "
|
|
"this may lead to font issues.");
|
|
#endif // DESKTOP_APP_USE_PACKAGED_FONTS
|
|
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
|
|
const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
|
|
char h[33] = { 0 };
|
|
hashMd5Hex(d.constData(), d.size(), h);
|
|
|
|
Webview::WebKit2Gtk::SetSocketPath(qsl("%1/%2-%3-webview-%4").arg(
|
|
QDir::tempPath(),
|
|
h,
|
|
cGUIDStr(),
|
|
qsl("%1")).toStdString());
|
|
}
|
|
|
|
void finish() {
|
|
}
|
|
|
|
void InstallLauncher(bool force) {
|
|
static const auto DisabledByEnv = !qEnvironmentVariableIsEmpty(
|
|
"DESKTOPINTEGRATION");
|
|
|
|
// don't update desktop file for alpha version or if updater is disabled
|
|
if ((cAlphaVersion() || Core::UpdaterDisabled() || DisabledByEnv)
|
|
&& !force) {
|
|
return;
|
|
}
|
|
|
|
const auto applicationsPath = QStandardPaths::writableLocation(
|
|
QStandardPaths::ApplicationsLocation) + '/';
|
|
|
|
GenerateDesktopFile(applicationsPath, qsl("-- %u"));
|
|
|
|
const auto icons = QStandardPaths::writableLocation(
|
|
QStandardPaths::GenericDataLocation) + qsl("/icons/");
|
|
|
|
if (!QDir(icons).exists()) QDir().mkpath(icons);
|
|
|
|
const auto icon = icons + base::IconName() + qsl(".png");
|
|
auto iconExists = QFile::exists(icon);
|
|
if (Local::oldSettingsVersion() < 2008012 && iconExists) {
|
|
// Icon was changed.
|
|
if (QFile::remove(icon)) {
|
|
iconExists = false;
|
|
}
|
|
}
|
|
if (!iconExists) {
|
|
if (QFile::copy(qsl(":/gui/art/logo_256.png"), icon)) {
|
|
DEBUG_LOG(("App Info: Icon copied to '%1'").arg(icon));
|
|
}
|
|
}
|
|
|
|
QProcess::execute("update-desktop-database", {
|
|
applicationsPath
|
|
});
|
|
}
|
|
|
|
PermissionStatus GetPermissionStatus(PermissionType type) {
|
|
return PermissionStatus::Granted;
|
|
}
|
|
|
|
void RequestPermission(PermissionType type, Fn<void(PermissionStatus)> resultCallback) {
|
|
resultCallback(PermissionStatus::Granted);
|
|
}
|
|
|
|
void OpenSystemSettingsForPermission(PermissionType type) {
|
|
}
|
|
|
|
bool OpenSystemSettings(SystemSettingsType type) {
|
|
if (type == SystemSettingsType::Audio) {
|
|
struct Command {
|
|
QString command;
|
|
QStringList arguments;
|
|
};
|
|
auto options = std::vector<Command>();
|
|
const auto add = [&](const char *option, const char *arg = nullptr) {
|
|
auto command = Command{ .command = option };
|
|
if (arg) {
|
|
command.arguments.push_back(arg);
|
|
}
|
|
options.push_back(std::move(command));
|
|
};
|
|
for (const auto &type : DesktopEnvironment::Get()) {
|
|
using DesktopEnvironment::Type;
|
|
if (type == Type::Unity) {
|
|
add("unity-control-center", "sound");
|
|
} else if (type == Type::KDE) {
|
|
add("kcmshell5", "kcm_pulseaudio");
|
|
add("kcmshell4", "phonon");
|
|
} else if (type == Type::Gnome) {
|
|
add("gnome-control-center", "sound");
|
|
} else if (type == Type::Cinnamon) {
|
|
add("cinnamon-settings", "sound");
|
|
} else if (type == Type::MATE) {
|
|
add("mate-volume-control");
|
|
}
|
|
}
|
|
add("pavucontrol-qt");
|
|
add("pavucontrol");
|
|
add("alsamixergui");
|
|
return ranges::any_of(options, [](const Command &command) {
|
|
return QProcess::startDetached(
|
|
command.command,
|
|
command.arguments);
|
|
});
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void NewVersionLaunched(int oldVersion) {
|
|
InstallLauncher();
|
|
if (oldVersion > 0
|
|
&& oldVersion <= 4000002
|
|
&& qEnvironmentVariableIsSet("WAYLAND_DISPLAY")
|
|
&& DesktopEnvironment::IsGnome()
|
|
&& !QFile::exists(cWorkingDir() + qsl("tdata/nowayland"))) {
|
|
QFile f(cWorkingDir() + qsl("tdata/nowayland"));
|
|
if (f.open(QIODevice::WriteOnly)) {
|
|
f.write("1");
|
|
f.close();
|
|
Core::Restart(); // restart with X backend
|
|
}
|
|
}
|
|
if (oldVersion <= 4001001 && cAutoStart()) {
|
|
AutostartToggle(true);
|
|
}
|
|
}
|
|
|
|
namespace ThirdParty {
|
|
|
|
void start() {
|
|
LOG(("Icon theme: %1").arg(QIcon::themeName()));
|
|
LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName()));
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
LaunchGApplication();
|
|
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
|
}
|
|
|
|
void finish() {
|
|
}
|
|
|
|
} // namespace ThirdParty
|
|
|
|
} // namespace Platform
|
|
|
|
void psSendToMenu(bool send, bool silent) {
|
|
}
|
|
|
|
bool linuxMoveFile(const char *from, const char *to) {
|
|
FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
|
|
if (!ffrom) {
|
|
if (fto) fclose(fto);
|
|
return false;
|
|
}
|
|
if (!fto) {
|
|
fclose(ffrom);
|
|
return false;
|
|
}
|
|
static const int BufSize = 65536;
|
|
char buf[BufSize];
|
|
while (size_t size = fread(buf, 1, BufSize, ffrom)) {
|
|
fwrite(buf, 1, size, fto);
|
|
}
|
|
|
|
struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c
|
|
//let's say this wont fail since you already worked OK on that fp
|
|
if (fstat(fileno(ffrom), &fst) != 0) {
|
|
fclose(ffrom);
|
|
fclose(fto);
|
|
return false;
|
|
}
|
|
//update to the same uid/gid
|
|
if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
|
|
fclose(ffrom);
|
|
fclose(fto);
|
|
return false;
|
|
}
|
|
//update the permissions
|
|
if (fchmod(fileno(fto), fst.st_mode) != 0) {
|
|
fclose(ffrom);
|
|
fclose(fto);
|
|
return false;
|
|
}
|
|
|
|
fclose(ffrom);
|
|
fclose(fto);
|
|
|
|
if (unlink(from)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool psLaunchMaps(const Data::LocationPoint &point) {
|
|
return false;
|
|
}
|