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 &parameters) {
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