1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "platform/linux/specific_linux.h"
9
10 #include "base/random.h"
11 #include "base/platform/base_platform_info.h"
12 #include "ui/platform/linux/ui_linux_wayland_integration.h"
13 #include "platform/linux/linux_desktop_environment.h"
14 #include "platform/linux/linux_wayland_integration.h"
15 #include "lang/lang_keys.h"
16 #include "mainwindow.h"
17 #include "storage/localstorage.h"
18 #include "core/sandbox.h"
19 #include "core/application.h"
20 #include "core/core_settings.h"
21 #include "core/update_checker.h"
22 #include "window/window_controller.h"
23 #include "webview/platform/linux/webview_linux_webkit2gtk.h"
24
25 #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
26 #include "base/platform/linux/base_linux_glibmm_helper.h"
27 #include "base/platform/linux/base_linux_dbus_utilities.h"
28 #include "base/platform/linux/base_linux_xdp_utilities.h"
29 #include "platform/linux/linux_xdp_file_dialog.h"
30 #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
31
32 #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
33 #include "base/platform/linux/base_linux_xcb_utilities.h"
34 #include "base/platform/linux/base_linux_xsettings.h"
35 #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
36
37 #include <QtWidgets/QApplication>
38 #include <QtWidgets/QStyle>
39 #include <QtCore/QStandardPaths>
40 #include <QtCore/QProcess>
41 #include <QtGui/QWindow>
42
43 #include <private/qguiapplication_p.h>
44
45 #if defined(Q_OS_FREEBSD) && !defined(__DragonFly__)
46 #include <malloc_np.h>
47 #else // Q_OS_FREEBSD
48 #include <jemalloc/jemalloc.h>
49 #endif // Q_OS_FREEBSD
50
51 #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
52 #include <glibmm.h>
53 #include <giomm.h>
54 #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
55
56 #include <sys/stat.h>
57 #include <sys/types.h>
58 #ifdef Q_OS_LINUX
59 #include <sys/sendfile.h>
60 #endif // Q_OS_LINUX
61 #include <cstdlib>
62 #include <unistd.h>
63 #include <dirent.h>
64 #include <pwd.h>
65
66 #include <iostream>
67
68 using namespace Platform;
69 using UiWaylandIntegration = Ui::Platform::WaylandIntegration;
70 using Platform::internal::WaylandIntegration;
71
72 namespace Platform {
73 namespace {
74
75 constexpr auto kDesktopFile = ":/misc/telegramdesktop.desktop"_cs;
76 constexpr auto kIconName = "telegram"_cs;
77 constexpr auto kDarkColorLimit = 192;
78
79 constexpr auto kXDGDesktopPortalService = "org.freedesktop.portal.Desktop"_cs;
80 constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_cs;
81 constexpr auto kIBusPortalService = "org.freedesktop.portal.IBus"_cs;
82 constexpr auto kWebviewService = "org.telegram.desktop.GtkIntegration.WebviewHelper-%1-%2"_cs;
83
84 #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
PortalAutostart(bool start,bool silent)85 void PortalAutostart(bool start, bool silent) {
86 if (cExeName().isEmpty()) {
87 return;
88 }
89
90 try {
91 const auto connection = Gio::DBus::Connection::get_sync(
92 Gio::DBus::BusType::BUS_TYPE_SESSION);
93
94 const auto parentWindowId = [&]() -> Glib::ustring {
95 std::stringstream result;
96
97 const auto activeWindow = Core::App().activeWindow();
98 if (!activeWindow) {
99 return result.str();
100 }
101
102 const auto window = activeWindow->widget()->windowHandle();
103 if (const auto integration = WaylandIntegration::Instance()) {
104 if (const auto handle = integration->nativeHandle(window)
105 ; !handle.isEmpty()) {
106 result << "wayland:" << handle.toStdString();
107 }
108 } else if (IsX11()) {
109 result << "x11:" << std::hex << window->winId();
110 }
111
112 return result.str();
113 }();
114
115 const auto handleToken = Glib::ustring("tdesktop")
116 + std::to_string(base::RandomValue<uint>());
117
118 std::map<Glib::ustring, Glib::VariantBase> options;
119 options["handle_token"] = Glib::Variant<Glib::ustring>::create(
120 handleToken);
121 options["reason"] = Glib::Variant<Glib::ustring>::create(
122 tr::lng_settings_auto_start(tr::now).toStdString());
123 options["autostart"] = Glib::Variant<bool>::create(start);
124 options["commandline"] = Glib::Variant<std::vector<
125 Glib::ustring
126 >>::create({
127 cExeName().toStdString(),
128 "-workdir",
129 cWorkingDir().toStdString(),
130 "-autostart",
131 });
132 options["dbus-activatable"] = Glib::Variant<bool>::create(false);
133
134 auto uniqueName = connection->get_unique_name();
135 uniqueName.erase(0, 1);
136 uniqueName.replace(uniqueName.find('.'), 1, 1, '_');
137
138 const auto requestPath = Glib::ustring(
139 "/org/freedesktop/portal/desktop/request/")
140 + uniqueName
141 + '/'
142 + handleToken;
143
144 const auto context = Glib::MainContext::create();
145 const auto loop = Glib::MainLoop::create(context);
146 g_main_context_push_thread_default(context->gobj());
147 const auto contextGuard = gsl::finally([&] {
148 g_main_context_pop_thread_default(context->gobj());
149 });
150
151 const auto signalId = connection->signal_subscribe(
152 [&](
153 const Glib::RefPtr<Gio::DBus::Connection> &connection,
154 const Glib::ustring &sender_name,
155 const Glib::ustring &object_path,
156 const Glib::ustring &interface_name,
157 const Glib::ustring &signal_name,
158 const Glib::VariantContainerBase ¶meters) {
159 try {
160 auto parametersCopy = parameters;
161
162 const auto response = base::Platform::GlibVariantCast<
163 uint>(parametersCopy.get_child(0));
164
165 if (response && !silent) {
166 LOG(("Portal Autostart Error: Request denied"));
167 }
168 } catch (const std::exception &e) {
169 if (!silent) {
170 LOG(("Portal Autostart Error: %1").arg(
171 QString::fromStdString(e.what())));
172 }
173 }
174
175 loop->quit();
176 },
177 std::string(kXDGDesktopPortalService),
178 "org.freedesktop.portal.Request",
179 "Response",
180 requestPath);
181
182 const auto signalGuard = gsl::finally([&] {
183 if (signalId != 0) {
184 connection->signal_unsubscribe(signalId);
185 }
186 });
187
188 connection->call_sync(
189 std::string(kXDGDesktopPortalObjectPath),
190 "org.freedesktop.portal.Background",
191 "RequestBackground",
192 base::Platform::MakeGlibVariant(std::tuple{
193 parentWindowId,
194 options,
195 }),
196 std::string(kXDGDesktopPortalService));
197
198 if (signalId != 0) {
199 QWindow window;
200 QGuiApplicationPrivate::showModalWindow(&window);
201 loop->run();
202 QGuiApplicationPrivate::hideModalWindow(&window);
203 }
204 } catch (const Glib::Error &e) {
205 if (!silent) {
206 LOG(("Portal Autostart Error: %1").arg(
207 QString::fromStdString(e.what())));
208 }
209 }
210 }
211
IsIBusPortalPresent()212 bool IsIBusPortalPresent() {
213 static const auto Result = [&] {
214 try {
215 const auto connection = Gio::DBus::Connection::get_sync(
216 Gio::DBus::BusType::BUS_TYPE_SESSION);
217
218 const auto serviceRegistered = base::Platform::DBus::NameHasOwner(
219 connection,
220 std::string(kIBusPortalService));
221
222 const auto serviceActivatable = ranges::contains(
223 base::Platform::DBus::ListActivatableNames(connection),
224 Glib::ustring(std::string(kIBusPortalService)));
225
226 return serviceRegistered || serviceActivatable;
227 } catch (...) {
228 }
229
230 return false;
231 }();
232
233 return Result;
234 }
235 #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
236
EscapeShell(const QByteArray & content)237 QByteArray EscapeShell(const QByteArray &content) {
238 auto result = QByteArray();
239
240 auto b = content.constData(), e = content.constEnd();
241 for (auto ch = b; ch != e; ++ch) {
242 if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
243 if (result.isEmpty()) {
244 result.reserve(content.size() * 2);
245 }
246 if (ch > b) {
247 result.append(b, ch - b);
248 }
249 result.append('\\');
250 b = ch;
251 }
252 }
253 if (result.isEmpty()) {
254 return content;
255 }
256
257 if (e > b) {
258 result.append(b, e - b);
259 }
260 return result;
261 }
262
EscapeShellInLauncher(const QString & content)263 QString EscapeShellInLauncher(const QString &content) {
264 return EscapeShell(content.toUtf8()).replace('\\', "\\\\");
265 }
266
FlatpakID()267 QString FlatpakID() {
268 static const auto Result = [] {
269 if (!qEnvironmentVariableIsEmpty("FLATPAK_ID")) {
270 return qEnvironmentVariable("FLATPAK_ID");
271 } else {
272 return cExeName();
273 }
274 }();
275
276 return Result;
277 }
278
GenerateDesktopFile(const QString & targetPath,const QString & args,bool silent=false)279 bool GenerateDesktopFile(
280 const QString &targetPath,
281 const QString &args,
282 bool silent = false) {
283 if (targetPath.isEmpty() || cExeName().isEmpty()) {
284 return false;
285 }
286
287 DEBUG_LOG(("App Info: placing .desktop file to %1").arg(targetPath));
288 if (!QDir(targetPath).exists()) QDir().mkpath(targetPath);
289
290 const auto sourceFile = kDesktopFile.utf16();
291 const auto targetFile = targetPath + QGuiApplication::desktopFileName();
292
293 QString fileText;
294
295 QFile source(sourceFile);
296 if (source.open(QIODevice::ReadOnly)) {
297 QTextStream s(&source);
298 fileText = s.readAll();
299 source.close();
300 } else {
301 if (!silent) {
302 LOG(("App Error: Could not open '%1' for read").arg(sourceFile));
303 }
304 return false;
305 }
306
307 QFile target(targetFile);
308 if (target.open(QIODevice::WriteOnly)) {
309 fileText = fileText.replace(
310 QRegularExpression(
311 qsl("^TryExec=.*$"),
312 QRegularExpression::MultilineOption),
313 qsl("TryExec=%1").arg(
314 QString(cExeDir() + cExeName()).replace('\\', "\\\\")));
315
316 fileText = fileText.replace(
317 QRegularExpression(
318 qsl("^Exec=telegram-desktop(.*)$"),
319 QRegularExpression::MultilineOption),
320 qsl("Exec=%1 -workdir %2\\1").arg(
321 EscapeShellInLauncher(cExeDir() + cExeName()),
322 EscapeShellInLauncher(cWorkingDir())));
323
324 fileText = fileText.replace(
325 QRegularExpression(
326 qsl("^Exec=(.*) -- %u$"),
327 QRegularExpression::MultilineOption),
328 qsl("Exec=\\1%1").arg(
329 args.isEmpty() ? QString() : ' ' + args));
330
331 target.write(fileText.toUtf8());
332 target.close();
333
334 if (!Core::UpdaterDisabled()) {
335 DEBUG_LOG(("App Info: removing old .desktop files"));
336 QFile::remove(qsl("%1telegram.desktop").arg(targetPath));
337 QFile::remove(qsl("%1telegramdesktop.desktop").arg(targetPath));
338 }
339
340 return true;
341 } else {
342 if (!silent) {
343 LOG(("App Error: Could not open '%1' for write").arg(targetFile));
344 }
345 return false;
346 }
347 }
348
349 } // namespace
350
SetApplicationIcon(const QIcon & icon)351 void SetApplicationIcon(const QIcon &icon) {
352 QApplication::setWindowIcon(icon);
353 }
354
InFlatpak()355 bool InFlatpak() {
356 static const auto Result = QFileInfo::exists(qsl("/.flatpak-info"));
357 return Result;
358 }
359
InSnap()360 bool InSnap() {
361 static const auto Result = qEnvironmentVariableIsSet("SNAP");
362 return Result;
363 }
364
AppRuntimeDirectory()365 QString AppRuntimeDirectory() {
366 static const auto Result = [&] {
367 auto runtimeDir = QStandardPaths::writableLocation(
368 QStandardPaths::RuntimeLocation);
369
370 if (InFlatpak()) {
371 runtimeDir += qsl("/app/") + FlatpakID();
372 }
373
374 if (!QFileInfo::exists(runtimeDir)) { // non-systemd distros
375 runtimeDir = QDir::tempPath();
376 }
377
378 if (!runtimeDir.endsWith('/')) {
379 runtimeDir += '/';
380 }
381
382 return runtimeDir;
383 }();
384
385 return Result;
386 }
387
SingleInstanceLocalServerName(const QString & hash)388 QString SingleInstanceLocalServerName(const QString &hash) {
389 const auto idealSocketPath = AppRuntimeDirectory()
390 + hash
391 + '-'
392 + cGUIDStr();
393
394 if (idealSocketPath.size() >= 108) {
395 return AppRuntimeDirectory() + hash;
396 } else {
397 return idealSocketPath;
398 }
399 }
400
GetIconName()401 QString GetIconName() {
402 static const auto Result = InFlatpak()
403 ? FlatpakID()
404 : kIconName.utf16();
405 return Result;
406 }
407
IsDarkMode()408 std::optional<bool> IsDarkMode() {
409 [[maybe_unused]] static const auto Inited = [] {
410 static const auto Setter = [] {
411 crl::on_main([] {
412 Core::App().settings().setSystemDarkMode(IsDarkMode());
413 });
414 };
415
416 QObject::connect(
417 qGuiApp,
418 &QGuiApplication::paletteChanged,
419 Setter);
420
421 #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
422 using base::Platform::XCB::XSettings;
423 if (const auto xSettings = XSettings::Instance()) {
424 xSettings->registerCallbackForProperty("Net/ThemeName", [](
425 xcb_connection_t *,
426 const QByteArray &,
427 const QVariant &,
428 void *) {
429 Setter();
430 }, nullptr);
431 }
432 #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
433
434 #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
435 using XDPSettingWatcher = base::Platform::XDP::SettingWatcher;
436 static const XDPSettingWatcher GtkThemeWatcher(
437 [=](
438 const Glib::ustring &group,
439 const Glib::ustring &key,
440 const Glib::VariantBase &value) {
441 if (group == "org.gnome.desktop.interface"
442 && key == "gtk-theme") {
443 Setter();
444 }
445 });
446
447 static const XDPSettingWatcher KdeColorSchemeWatcher(
448 [=](
449 const Glib::ustring &group,
450 const Glib::ustring &key,
451 const Glib::VariantBase &value) {
452 if (group == "org.kde.kdeglobals.General"
453 && key == "ColorScheme") {
454 Setter();
455 }
456 });
457 #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
458
459 return true;
460 }();
461
462 std::optional<bool> result;
463
464 const auto styleName = QApplication::style()->metaObject()->className();
465 if (styleName != qstr("QFusionStyle")
466 && styleName != qstr("QWindowsStyle")) {
467 result = false;
468
469 const auto paletteBackgroundGray = qGray(
470 QPalette().color(QPalette::Window).rgb());
471
472 if (paletteBackgroundGray < kDarkColorLimit) {
473 result = true;
474 return result;
475 }
476 }
477
478 #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
479 using base::Platform::XCB::XSettings;
480 if (const auto xSettings = XSettings::Instance()) {
481 const auto gtkThemeX = xSettings->setting("Net/ThemeName");
482 if (gtkThemeX.isValid()) {
483 result = false;
484 if (gtkThemeX.toString().contains(
485 qsl("-dark"),
486 Qt::CaseInsensitive)) {
487 result = true;
488 return result;
489 }
490 }
491 }
492 #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
493
494 #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
495 try {
496 using namespace base::Platform::XDP;
497
498 const auto gtkThemePortal = ReadSetting(
499 "org.gnome.desktop.interface",
500 "gtk-theme");
501
502 if (gtkThemePortal.has_value()) {
503 const auto gtkThemePortalString = QString::fromStdString(
504 base::Platform::GlibVariantCast<Glib::ustring>(
505 *gtkThemePortal));
506
507 result = false;
508
509 if (gtkThemePortalString.contains(
510 qsl("-dark"),
511 Qt::CaseInsensitive)) {
512 result = true;
513 return result;
514 }
515 }
516 } catch (...) {
517 }
518
519 try {
520 using namespace base::Platform::XDP;
521
522 const auto kdeBackgroundColorOptional = ReadSetting(
523 "org.kde.kdeglobals.Colors:Window",
524 "BackgroundNormal");
525
526 if (kdeBackgroundColorOptional.has_value()) {
527 const auto kdeBackgroundColorList = QString::fromStdString(
528 base::Platform::GlibVariantCast<Glib::ustring>(
529 *kdeBackgroundColorOptional)).split(',');
530
531 if (kdeBackgroundColorList.size() >= 3) {
532 result = false;
533
534 const auto kdeBackgroundGray = qGray(
535 kdeBackgroundColorList[0].toInt(),
536 kdeBackgroundColorList[1].toInt(),
537 kdeBackgroundColorList[2].toInt());
538
539 if (kdeBackgroundGray < kDarkColorLimit) {
540 result = true;
541 return result;
542 }
543 }
544 }
545 } catch (...) {
546 }
547 #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
548
549 return result;
550 }
551
AutostartSupported()552 bool AutostartSupported() {
553 // snap sandbox doesn't allow creating files
554 // in folders with names started with a dot
555 // and doesn't provide any api to add an app to autostart
556 // thus, autostart isn't supported in snap
557 return !InSnap();
558 }
559
AutostartToggle(bool enabled,Fn<void (bool)> done)560 void AutostartToggle(bool enabled, Fn<void(bool)> done) {
561 const auto guard = gsl::finally([&] {
562 if (done) {
563 done(enabled);
564 }
565 });
566
567 #ifdef __HAIKU__
568
569 HaikuAutostart(enabled);
570
571 #else // __HAIKU__
572
573 const auto silent = !done;
574 if (InFlatpak()) {
575 #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
576 PortalAutostart(enabled, silent);
577 #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
578 } else {
579 const auto autostart = QStandardPaths::writableLocation(
580 QStandardPaths::GenericConfigLocation)
581 + qsl("/autostart/");
582
583 if (enabled) {
584 GenerateDesktopFile(autostart, qsl("-autostart"), silent);
585 } else {
586 QFile::remove(autostart + QGuiApplication::desktopFileName());
587 }
588 }
589
590 #endif // __HAIKU__
591 }
592
AutostartSkip()593 bool AutostartSkip() {
594 return !cAutoStart();
595 }
596
TrayIconSupported()597 bool TrayIconSupported() {
598 return App::wnd()
599 ? App::wnd()->trayAvailable()
600 : false;
601 }
602
SkipTaskbarSupported()603 bool SkipTaskbarSupported() {
604 if (const auto integration = WaylandIntegration::Instance()) {
605 return integration->skipTaskbarSupported();
606 }
607
608 #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION
609 if (IsX11()) {
610 return base::Platform::XCB::IsSupportedByWM(
611 "_NET_WM_STATE_SKIP_TASKBAR");
612 }
613 #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION
614
615 return false;
616 }
617
618 } // namespace Platform
619
psActivateProcess(uint64 pid)620 void psActivateProcess(uint64 pid) {
621 // objc_activateProgram();
622 }
623
624 namespace {
625
626 #ifdef __HAIKU__
HaikuAutostart(bool start)627 void HaikuAutostart(bool start) {
628 const auto home = QDir::homePath();
629 if (home.isEmpty()) {
630 return;
631 }
632
633 QFile file(home + "/config/settings/boot/launch/telegram-desktop");
634 if (start) {
635 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
636 QTextStream out(&file);
637 out
638 << "#!/bin/bash" << Qt::endl
639 << "cd /system/apps" << Qt::endl
640 << "./Telegram -autostart" << " &" << Qt::endl;
641 file.close();
642 file.setPermissions(file.permissions()
643 | QFileDevice::ExeOwner
644 | QFileDevice::ExeGroup
645 | QFileDevice::ExeOther);
646 }
647 } else {
648 file.remove();
649 }
650 }
651 #endif // __HAIKU__
652
653 } // namespace
654
psAppDataPath()655 QString psAppDataPath() {
656 // Previously we used ~/.TelegramDesktop, so look there first.
657 // If we find data there, we should still use it.
658 auto home = QDir::homePath();
659 if (!home.isEmpty()) {
660 auto oldPath = home + qsl("/.TelegramDesktop/");
661 auto oldSettingsBase = oldPath + qsl("tdata/settings");
662 if (QFile::exists(oldSettingsBase + '0')
663 || QFile::exists(oldSettingsBase + '1')
664 || QFile::exists(oldSettingsBase + 's')) {
665 return oldPath;
666 }
667 }
668
669 return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + '/';
670 }
671
psDoCleanup()672 void psDoCleanup() {
673 try {
674 Platform::AutostartToggle(false);
675 psSendToMenu(false, true);
676 } catch (...) {
677 }
678 }
679
psCleanup()680 int psCleanup() {
681 psDoCleanup();
682 return 0;
683 }
684
psDoFixPrevious()685 void psDoFixPrevious() {
686 }
687
psFixPrevious()688 int psFixPrevious() {
689 psDoFixPrevious();
690 return 0;
691 }
692
693 namespace Platform {
694
start()695 void start() {
696 auto backgroundThread = true;
697 mallctl("background_thread", nullptr, nullptr, &backgroundThread, sizeof(bool));
698
699 // Prevent any later calls into setlocale() by Qt
700 QCoreApplicationPrivate::initLocale();
701
702 LOG(("Launcher filename: %1").arg(QGuiApplication::desktopFileName()));
703
704 #ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
705 qputenv("QT_WAYLAND_SHELL_INTEGRATION", "desktop-app-xdg-shell;xdg-shell");
706 #endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION
707
708 qputenv("PULSE_PROP_application.name", AppName.utf8());
709 qputenv("PULSE_PROP_application.icon_name", GetIconName().toLatin1());
710
711 #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
712 Glib::set_prgname(cExeName().toStdString());
713 Glib::set_application_name(std::string(AppName));
714
715 #ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE
716 g_warning(
717 "Application has been built with foreign rlottie, "
718 "animated emojis won't be colored to the selected pack.");
719 #endif // DESKTOP_APP_USE_PACKAGED_RLOTTIE
720
721 #ifdef DESKTOP_APP_USE_PACKAGED_FONTS
722 g_warning(
723 "Application was built without embedded fonts, "
724 "this may lead to font issues.");
725 #endif // DESKTOP_APP_USE_PACKAGED_FONTS
726
727 // IBus has changed its socket path several times
728 // and each change should be synchronized with Qt.
729 // Moreover, the last time Qt changed the path,
730 // they didn't introduce a fallback to the old path
731 // and made the new Qt incompatible with IBus from older distributions.
732 // Since tdesktop is distributed in static binary form,
733 // it makes sense to use ibus portal whenever it present
734 // to ensure compatibility with the maximum range of distributions.
735 if (IsIBusPortalPresent()) {
736 LOG(("IBus portal is present! Using it."));
737 qputenv("IBUS_USE_PORTAL", "1");
738 }
739 #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
740
741 const auto d = QFile::encodeName(QDir(cWorkingDir()).absolutePath());
742 char h[33] = { 0 };
743 hashMd5Hex(d.constData(), d.size(), h);
744
745 Webview::WebKit2Gtk::SetServiceName(
746 kWebviewService.utf16().arg(h).arg("%1").toStdString());
747 }
748
finish()749 void finish() {
750 }
751
InstallLauncher(bool force)752 void InstallLauncher(bool force) {
753 static const auto DisabledByEnv = !qEnvironmentVariableIsEmpty(
754 "DESKTOPINTEGRATION");
755
756 // don't update desktop file for alpha version or if updater is disabled
757 if ((cAlphaVersion() || Core::UpdaterDisabled() || DisabledByEnv)
758 && !force) {
759 return;
760 }
761
762 const auto applicationsPath = QStandardPaths::writableLocation(
763 QStandardPaths::ApplicationsLocation) + '/';
764
765 GenerateDesktopFile(applicationsPath, qsl("-- %u"));
766
767 const auto icons = QStandardPaths::writableLocation(
768 QStandardPaths::GenericDataLocation) + qsl("/icons/");
769
770 if (!QDir(icons).exists()) QDir().mkpath(icons);
771
772 const auto icon = icons + kIconName.utf16() + qsl(".png");
773 auto iconExists = QFile::exists(icon);
774 if (Local::oldSettingsVersion() < 2008012 && iconExists) {
775 // Icon was changed.
776 if (QFile::remove(icon)) {
777 iconExists = false;
778 }
779 }
780 if (!iconExists) {
781 if (QFile::copy(qsl(":/gui/art/logo_256.png"), icon)) {
782 DEBUG_LOG(("App Info: Icon copied to '%1'").arg(icon));
783 }
784 }
785
786 QProcess::execute("update-desktop-database", {
787 applicationsPath
788 });
789 }
790
GetPermissionStatus(PermissionType type)791 PermissionStatus GetPermissionStatus(PermissionType type) {
792 return PermissionStatus::Granted;
793 }
794
RequestPermission(PermissionType type,Fn<void (PermissionStatus)> resultCallback)795 void RequestPermission(PermissionType type, Fn<void(PermissionStatus)> resultCallback) {
796 resultCallback(PermissionStatus::Granted);
797 }
798
OpenSystemSettingsForPermission(PermissionType type)799 void OpenSystemSettingsForPermission(PermissionType type) {
800 }
801
OpenSystemSettings(SystemSettingsType type)802 bool OpenSystemSettings(SystemSettingsType type) {
803 if (type == SystemSettingsType::Audio) {
804 struct Command {
805 QString command;
806 QStringList arguments;
807 };
808 auto options = std::vector<Command>();
809 const auto add = [&](const char *option, const char *arg = nullptr) {
810 auto command = Command{ .command = option };
811 if (arg) {
812 command.arguments.push_back(arg);
813 }
814 options.push_back(std::move(command));
815 };
816 if (DesktopEnvironment::IsUnity()) {
817 add("unity-control-center", "sound");
818 } else if (DesktopEnvironment::IsKDE()) {
819 add("kcmshell5", "kcm_pulseaudio");
820 add("kcmshell4", "phonon");
821 } else if (DesktopEnvironment::IsGnome()) {
822 add("gnome-control-center", "sound");
823 } else if (DesktopEnvironment::IsCinnamon()) {
824 add("cinnamon-settings", "sound");
825 } else if (DesktopEnvironment::IsMATE()) {
826 add("mate-volume-control");
827 }
828 #ifdef __HAIKU__
829 add("Media");
830 #endif // __ HAIKU__
831 add("pavucontrol-qt");
832 add("pavucontrol");
833 add("alsamixergui");
834 return ranges::any_of(options, [](const Command &command) {
835 return QProcess::startDetached(
836 command.command,
837 command.arguments);
838 });
839 }
840 return true;
841 }
842
843 namespace ThirdParty {
844
start()845 void start() {
846 LOG(("Icon theme: %1").arg(QIcon::themeName()));
847 LOG(("Fallback icon theme: %1").arg(QIcon::fallbackThemeName()));
848
849 // wait for interface announce to know if native window frame is supported
850 if (const auto integration = UiWaylandIntegration::Instance()) {
851 integration->waitForInterfaceAnnounce();
852 }
853
854 #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
855 FileDialog::XDP::Start();
856 #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
857 }
858
finish()859 void finish() {
860 }
861
862 } // namespace ThirdParty
863
864 } // namespace Platform
865
psNewVersion()866 void psNewVersion() {
867 #ifndef __HAIKU__
868 Platform::InstallLauncher();
869 #endif // __HAIKU__
870 }
871
psSendToMenu(bool send,bool silent)872 void psSendToMenu(bool send, bool silent) {
873 }
874
sendfileFallback(FILE * out,FILE * in)875 void sendfileFallback(FILE *out, FILE *in) {
876 static const int BufSize = 65536;
877 char buf[BufSize];
878 while (size_t size = fread(buf, 1, BufSize, in)) {
879 fwrite(buf, 1, size, out);
880 }
881 }
882
linuxMoveFile(const char * from,const char * to)883 bool linuxMoveFile(const char *from, const char *to) {
884 FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
885 if (!ffrom) {
886 if (fto) fclose(fto);
887 return false;
888 }
889 if (!fto) {
890 fclose(ffrom);
891 return false;
892 }
893
894 struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c
895 //let's say this wont fail since you already worked OK on that fp
896 if (fstat(fileno(ffrom), &fst) != 0) {
897 fclose(ffrom);
898 fclose(fto);
899 return false;
900 }
901
902 #ifdef Q_OS_LINUX
903 ssize_t copied = sendfile(
904 fileno(fto),
905 fileno(ffrom),
906 nullptr,
907 fst.st_size);
908 if (copied == -1) {
909 DEBUG_LOG(("Update Error: "
910 "Copy by sendfile '%1' to '%2' failed, error: %3, fallback now."
911 ).arg(from
912 ).arg(to
913 ).arg(errno));
914 sendfileFallback(fto, ffrom);
915 } else {
916 DEBUG_LOG(("Update Info: "
917 "Copy by sendfile '%1' to '%2' done, size: %3, result: %4."
918 ).arg(from
919 ).arg(to
920 ).arg(fst.st_size
921 ).arg(copied));
922 }
923 #else // Q_OS_LINUX
924 sendfileFallback(fto, ffrom);
925 #endif // Q_OS_LINUX
926
927 //update to the same uid/gid
928 if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
929 fclose(ffrom);
930 fclose(fto);
931 return false;
932 }
933 //update the permissions
934 if (fchmod(fileno(fto), fst.st_mode) != 0) {
935 fclose(ffrom);
936 fclose(fto);
937 return false;
938 }
939
940 fclose(ffrom);
941 fclose(fto);
942
943 if (unlink(from)) {
944 return false;
945 }
946
947 return true;
948 }
949
psLaunchMaps(const Data::LocationPoint & point)950 bool psLaunchMaps(const Data::LocationPoint &point) {
951 return false;
952 }
953