From 404538c98968d44d207671de3adde91b8df50721 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 29 Apr 2021 11:21:38 +0400 Subject: [PATCH 001/477] Fix build with dummy notifications manager on Linux. --- Telegram/CMakeLists.txt | 29 ++++++++++++------- .../notifications_manager_linux_dummy.cpp | 6 ++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c2868db11..cd620a43a 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -830,6 +830,7 @@ PRIVATE platform/linux/linux_gsd_media_keys.h platform/linux/linux_gtk_file_dialog.cpp platform/linux/linux_gtk_file_dialog.h + platform/linux/linux_gtk_integration_dummy.cpp platform/linux/linux_gtk_integration_p.h platform/linux/linux_gtk_integration.cpp platform/linux/linux_gtk_integration.h @@ -839,6 +840,7 @@ PRIVATE platform/linux/linux_mpris_support.h platform/linux/linux_notification_service_watcher.cpp platform/linux/linux_notification_service_watcher.h + platform/linux/linux_wayland_integration_dummy.cpp platform/linux/linux_wayland_integration.cpp platform/linux/linux_wayland_integration.h platform/linux/linux_xdp_file_dialog.cpp @@ -851,6 +853,7 @@ PRIVATE platform/linux/launcher_linux.h platform/linux/main_window_linux.cpp platform/linux/main_window_linux.h + platform/linux/notifications_manager_linux_dummy.cpp platform/linux/notifications_manager_linux.cpp platform/linux/notifications_manager_linux.h platform/linux/specific_linux.cpp @@ -860,6 +863,7 @@ PRIVATE platform/mac/file_utilities_mac.h platform/mac/launcher_mac.mm platform/mac/launcher_mac.h + platform/mac/mac_iconv_helper.c platform/mac/main_window_mac.mm platform/mac/main_window_mac.h platform/mac/notifications_manager_mac.mm @@ -1135,16 +1139,20 @@ if (DESKTOP_APP_DISABLE_DBUS_INTEGRATION) platform/linux/linux_xdp_open_with_dialog.h platform/linux/notifications_manager_linux.cpp ) - - nice_target_sources(Telegram ${src_loc} - PRIVATE +else() + remove_target_sources(Telegram ${src_loc} platform/linux/notifications_manager_linux_dummy.cpp ) endif() if (DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION) - remove_target_sources(Telegram ${src_loc} platform/linux/linux_wayland_integration.cpp) - nice_target_sources(Telegram ${src_loc} PRIVATE platform/linux/linux_wayland_integration_dummy.cpp) + remove_target_sources(Telegram ${src_loc} + platform/linux/linux_wayland_integration.cpp + ) +else() + remove_target_sources(Telegram ${src_loc} + platform/linux/linux_wayland_integration_dummy.cpp + ) endif() if (DESKTOP_APP_DISABLE_GTK_INTEGRATION) @@ -1158,15 +1166,16 @@ if (DESKTOP_APP_DISABLE_GTK_INTEGRATION) platform/linux/linux_gtk_open_with_dialog.cpp platform/linux/linux_gtk_open_with_dialog.h ) - - nice_target_sources(Telegram ${src_loc} - PRIVATE +else() + remove_target_sources(Telegram ${src_loc} platform/linux/linux_gtk_integration_dummy.cpp ) endif() -if (NOT DESKTOP_APP_USE_PACKAGED) - nice_target_sources(Telegram ${src_loc} PRIVATE platform/mac/mac_iconv_helper.c) +if (DESKTOP_APP_USE_PACKAGED) + remove_target_sources(Telegram ${src_loc} + platform/mac/mac_iconv_helper.c + ) endif() nice_target_sources(Telegram ${res_loc} diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux_dummy.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux_dummy.cpp index c51b92fed..0135a66b5 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux_dummy.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux_dummy.cpp @@ -13,15 +13,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Platform { namespace Notifications { -bool SkipAudio() { +bool SkipAudioForCustom() { return false; } -bool SkipToast() { +bool SkipToastForCustom() { return false; } -bool SkipFlashBounce() { +bool SkipFlashBounceForCustom() { return false; } From 70bf328e7deae6c863863e5e39d0d7af0fba279b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 29 Apr 2021 10:36:27 +0400 Subject: [PATCH 002/477] Load wayland-client dynamically and provide functions. --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 5 +++++ Telegram/lib_base | 2 +- Telegram/lib_webview | 2 +- cmake | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 7ea7d2a1a..8e3eea6dc 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_glibmm_helper.h" #include "base/platform/linux/base_linux_gtk_integration.h" +#include "base/platform/linux/base_linux_wayland_integration.h" #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/linux_gtk_integration.h" #include "platform/linux/linux_wayland_integration.h" @@ -756,6 +757,10 @@ void start() { integration->load(); } + if (IsWayland() && !base::Platform::ResolveWayland()) { + g_warning("Could not load wayland-client dynamically."); + } + #ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE g_warning( "Application has been built with foreign rlottie, " diff --git a/Telegram/lib_base b/Telegram/lib_base index 356daf59b..613cfe6d8 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 356daf59b35b2086cf5527e1c255557967fbb0af +Subproject commit 613cfe6d80c6c416571596f8a27da1550495d5e0 diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 91c0e6664..0b4100d7c 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 91c0e6664db4a444d630e3a0101f599a92df1dd9 +Subproject commit 0b4100d7cecc4e748c51f3f51ebfd1392ec3978a diff --git a/cmake b/cmake index 52ccf5e17..d1a7e08aa 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 52ccf5e17ab1bd7b352346c43706dc5e53bd19ca +Subproject commit d1a7e08aa044c2ace3c6b17ddf83cf971b31c46a From b6fad351463a6f6470a67e6c45b534ca27a8ab4d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 29 Apr 2021 12:05:32 +0400 Subject: [PATCH 003/477] Improve library loading on Linux. --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 2 +- Telegram/lib_base | 2 +- Telegram/lib_webview | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 8e3eea6dc..0a2348f17 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -11,7 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_glibmm_helper.h" #include "base/platform/linux/base_linux_gtk_integration.h" -#include "base/platform/linux/base_linux_wayland_integration.h" +#include "base/platform/linux/base_linux_wayland_helper.h" #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/linux_gtk_integration.h" #include "platform/linux/linux_wayland_integration.h" diff --git a/Telegram/lib_base b/Telegram/lib_base index 613cfe6d8..1b949f5ca 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 613cfe6d80c6c416571596f8a27da1550495d5e0 +Subproject commit 1b949f5ca250a47cd1261dd27fd83c174665f8fb diff --git a/Telegram/lib_webview b/Telegram/lib_webview index 0b4100d7c..48d70e525 160000 --- a/Telegram/lib_webview +++ b/Telegram/lib_webview @@ -1 +1 @@ -Subproject commit 0b4100d7cecc4e748c51f3f51ebfd1392ec3978a +Subproject commit 48d70e5258754b2415b07c88f0376212596e09a7 From 26d3995424a3ca34e11fec4cc845bf5ea5b8aefd Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 29 Apr 2021 19:49:22 +0400 Subject: [PATCH 004/477] Move wayland helper to cmake_helpers --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 0a2348f17..7ea7d2a1a 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_glibmm_helper.h" #include "base/platform/linux/base_linux_gtk_integration.h" -#include "base/platform/linux/base_linux_wayland_helper.h" #include "platform/linux/linux_desktop_environment.h" #include "platform/linux/linux_gtk_integration.h" #include "platform/linux/linux_wayland_integration.h" @@ -757,10 +756,6 @@ void start() { integration->load(); } - if (IsWayland() && !base::Platform::ResolveWayland()) { - g_warning("Could not load wayland-client dynamically."); - } - #ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE g_warning( "Application has been built with foreign rlottie, " From 89b11ef0845020b5608c58cfde9b0dfe1229b1f0 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 29 Apr 2021 19:49:49 +0400 Subject: [PATCH 005/477] Move gtk initialization back to ThirdParty::start --- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 7ea7d2a1a..339f61c5d 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -747,15 +747,10 @@ void start() { if (const auto integration = BaseGtkIntegration::Instance()) { integration->prepareEnvironment(); - integration->load(); } else { g_warning("GTK integration is disabled, some features unavailable."); } - if (const auto integration = GtkIntegration::Instance()) { - integration->load(); - } - #ifdef DESKTOP_APP_USE_PACKAGED_RLOTTIE g_warning( "Application has been built with foreign rlottie, " @@ -952,9 +947,14 @@ namespace ThirdParty { void start() { if (const auto integration = BaseGtkIntegration::Instance()) { + integration->load(); integration->initializeSettings(); } + if (const auto integration = GtkIntegration::Instance()) { + integration->load(); + } + SetGtkScaleFactor(); // wait for interface announce to know if native window frame is supported From 41078869a97ceba1e12335b1835649764a643fb4 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 29 Apr 2021 21:18:33 +0400 Subject: [PATCH 006/477] Update submodules --- Telegram/lib_base | 2 +- cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index 1b949f5ca..d01871440 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 1b949f5ca250a47cd1261dd27fd83c174665f8fb +Subproject commit d0187144060eb6689c0bfd5ddd44854561e929c6 diff --git a/cmake b/cmake index d1a7e08aa..6d1b48811 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit d1a7e08aa044c2ace3c6b17ddf83cf971b31c46a +Subproject commit 6d1b48811e9f3c74072aa1cb9e0ea07d17985fe1 From 0d96657c33af13b7e8c40c9316173281ef1c24db Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 30 Apr 2021 06:53:39 +0400 Subject: [PATCH 007/477] Fix check for disconnected error --- .../platform/linux/linux_xdp_file_dialog.cpp | 20 ++++++++++++------- .../platform/linux/main_window_linux.cpp | 15 +++++++++++--- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp index 1d960593f..86e652ed3 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp @@ -71,8 +71,18 @@ auto MakeFilterList(const QString &filter) { std::optional FileChooserPortalVersion() { try { - const auto connection = Gio::DBus::Connection::get_sync( - Gio::DBus::BusType::BUS_TYPE_SESSION); + const auto connection = [] { + try { + return Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION); + } catch (...) { + return Glib::RefPtr(); + } + }(); + + if (!connection) { + return std::nullopt; + } auto reply = connection->call_sync( std::string(kXDGDesktopPortalObjectPath), @@ -90,7 +100,6 @@ std::optional FileChooserPortalVersion() { reply.get_child(0))); } catch (const Glib::Error &e) { static const auto NotSupportedErrors = { - "org.freedesktop.DBus.Error.Disconnected", "org.freedesktop.DBus.Error.ServiceUnknown", }; @@ -435,10 +444,7 @@ void XDPFileDialog::openPortal() { }, _cancellable, std::string(kXDGDesktopPortalService)); - } catch (const Glib::Error &e) { - LOG(("XDP File Dialog Error: %1").arg( - QString::fromStdString(e.what()))); - + } catch (...) { _reject.fire({}); } } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 624e5107a..c6a9f6dae 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -430,8 +430,18 @@ bool UseUnityCounter() { bool IsSNIAvailable() { try { - const auto connection = Gio::DBus::Connection::get_sync( - Gio::DBus::BusType::BUS_TYPE_SESSION); + const auto connection = [] { + try { + return Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION); + } catch (...) { + return Glib::RefPtr(); + } + }(); + + if (!connection) { + return false; + } auto reply = connection->call_sync( std::string(kSNIWatcherObjectPath), @@ -448,7 +458,6 @@ bool IsSNIAvailable() { reply.get_child(0))); } catch (const Glib::Error &e) { static const auto NotSupportedErrors = { - "org.freedesktop.DBus.Error.Disconnected", "org.freedesktop.DBus.Error.ServiceUnknown", }; From 896eee9841d771281b5e618ba4e422843ab71880 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 30 Apr 2021 06:56:19 +0400 Subject: [PATCH 008/477] Check whether portal dialog is failed to open --- .../platform/linux/file_utilities_linux.cpp | 6 ++- .../platform/linux/linux_xdp_file_dialog.cpp | 44 ++++++++++++++++++- .../platform/linux/linux_xdp_file_dialog.h | 2 +- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index 92dc89179..7f272b9ca 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp @@ -172,7 +172,7 @@ bool Get( } #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION if (XDP::Use(type)) { - return XDP::Get( + const auto result = XDP::Get( parent, files, remoteContent, @@ -180,6 +180,10 @@ bool Get( filter, type, startFile); + + if (result.has_value()) { + return *result; + } } #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION if (const auto integration = GtkIntegration::Instance()) { diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp index 86e652ed3..97f5e0202 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp @@ -180,6 +180,10 @@ public: int exec() override; + bool failedToOpen() { + return _failedToOpen; + } + private: void openPortal(); void gotResponse( @@ -219,6 +223,7 @@ private: Glib::ustring _selectedMimeTypeFilter; Glib::ustring _selectedNameFilter; std::vector _selectedFiles; + bool _failedToOpen = false; rpl::event_stream<> _accept; rpl::event_stream<> _reject; @@ -432,12 +437,40 @@ void XDPFileDialog::openPortal() { }), [=](const Glib::RefPtr &result) { try { - _dbusConnection->call_finish(result); + auto reply = _dbusConnection->call_finish(result); + + const auto handle = base::Platform::GlibVariantCast< + Glib::ustring>(reply.get_child(0)); + + if (handle != requestPath) { + crl::on_main([=] { + _failedToOpen = true; + _reject.fire({}); + }); + } } catch (const Glib::Error &e) { + static const auto NotSupportedErrors = { + "org.freedesktop.DBus.Error.ServiceUnknown", + }; + + const auto errorName = + Gio::DBus::ErrorUtils::get_remote_error(e); + + if (!ranges::contains(NotSupportedErrors, errorName)) { + LOG(("XDP File Dialog Error: %1").arg( + QString::fromStdString(e.what()))); + } + + crl::on_main([=] { + _failedToOpen = true; + _reject.fire({}); + }); + } catch (const std::exception &e) { LOG(("XDP File Dialog Error: %1").arg( QString::fromStdString(e.what()))); crl::on_main([=] { + _failedToOpen = true; _reject.fire({}); }); } @@ -445,6 +478,7 @@ void XDPFileDialog::openPortal() { _cancellable, std::string(kXDGDesktopPortalService)); } catch (...) { + _failedToOpen = true; _reject.fire({}); } } @@ -502,6 +536,9 @@ int XDPFileDialog::exec() { setResult(0); show(); + if (failedToOpen()) { + return result(); + } QPointer guard = this; @@ -651,7 +688,7 @@ bool Use(Type type) { && (type != Type::ReadFolder || *Version >= 3); } -bool Get( +std::optional Get( QPointer parent, QStringList &files, QByteArray &remoteContent, @@ -688,6 +725,9 @@ bool Get( dialog.selectFile(startFile); const auto res = dialog.exec(); + if (dialog.failedToOpen()) { + return std::nullopt; + } if (type != Type::ReadFolder) { // Save last used directory for all queries except directory choosing. diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h index 1ded00e06..3c1cdc95a 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h @@ -16,7 +16,7 @@ namespace XDP { using Type = ::FileDialog::internal::Type; bool Use(Type type = Type::ReadFile); -bool Get( +std::optional Get( QPointer parent, QStringList &files, QByteArray &remoteContent, From b72260f42035eef3dcf390268446c2efdc09fd8e Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 30 Apr 2021 08:17:37 +0400 Subject: [PATCH 009/477] Avoid 30s freeze when opening file dialog in broken envirionments --- .../platform/linux/linux_xdp_file_dialog.cpp | 99 ++++++++++--------- .../platform/linux/linux_xdp_file_dialog.h | 1 + .../platform/linux/specific_linux.cpp | 2 + 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp index 97f5e0202..ffb651993 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp @@ -33,6 +33,8 @@ constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs; const char *filterRegExp = "^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$"; +std::optional FileChooserPortalVersion; + auto QStringListToStd(const QStringList &list) { std::vector result; ranges::transform( @@ -69,53 +71,55 @@ auto MakeFilterList(const QString &filter) { return result; } -std::optional FileChooserPortalVersion() { - try { - const auto connection = [] { - try { - return Gio::DBus::Connection::get_sync( - Gio::DBus::BusType::BUS_TYPE_SESSION); - } catch (...) { - return Glib::RefPtr(); - } - }(); - - if (!connection) { - return std::nullopt; +void ComputeFileChooserPortalVersion() { + const auto connection = [] { + try { + return Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION); + } catch (...) { + return Glib::RefPtr(); } + }(); - auto reply = connection->call_sync( - std::string(kXDGDesktopPortalObjectPath), - std::string(kPropertiesInterface), - "Get", - base::Platform::MakeGlibVariant(std::tuple{ - Glib::ustring( - std::string(kXDGDesktopPortalFileChooserInterface)), - Glib::ustring("version"), - }), - std::string(kXDGDesktopPortalService)); - - return base::Platform::GlibVariantCast( - base::Platform::GlibVariantCast( - reply.get_child(0))); - } catch (const Glib::Error &e) { - static const auto NotSupportedErrors = { - "org.freedesktop.DBus.Error.ServiceUnknown", - }; - - const auto errorName = Gio::DBus::ErrorUtils::get_remote_error(e); - if (ranges::contains(NotSupportedErrors, errorName)) { - return std::nullopt; - } - - LOG(("XDP File Dialog Error: %1").arg( - QString::fromStdString(e.what()))); - } catch (const std::exception &e) { - LOG(("XDP File Dialog Error: %1").arg( - QString::fromStdString(e.what()))); + if (!connection) { + return; } - return std::nullopt; + connection->call( + std::string(kXDGDesktopPortalObjectPath), + std::string(kPropertiesInterface), + "Get", + base::Platform::MakeGlibVariant(std::tuple{ + Glib::ustring( + std::string(kXDGDesktopPortalFileChooserInterface)), + Glib::ustring("version"), + }), + [=](const Glib::RefPtr &result) { + try { + auto reply = connection->call_finish(result); + + FileChooserPortalVersion = + base::Platform::GlibVariantCast( + base::Platform::GlibVariantCast( + reply.get_child(0))); + } catch (const Glib::Error &e) { + static const auto NotSupportedErrors = { + "org.freedesktop.DBus.Error.ServiceUnknown", + }; + + const auto errorName = + Gio::DBus::ErrorUtils::get_remote_error(e); + + if (!ranges::contains(NotSupportedErrors, errorName)) { + LOG(("XDP File Dialog Error: %1").arg( + QString::fromStdString(e.what()))); + } + } catch (const std::exception &e) { + LOG(("XDP File Dialog Error: %1").arg( + QString::fromStdString(e.what()))); + } + }, + std::string(kXDGDesktopPortalService)); } // This is a patched copy of file dialog from qxdgdesktopportal theme plugin. @@ -682,10 +686,13 @@ rpl::producer<> XDPFileDialog::rejected() { } // namespace +void Start() { + ComputeFileChooserPortalVersion(); +} + bool Use(Type type) { - static const auto Version = FileChooserPortalVersion(); - return Version.has_value() - && (type != Type::ReadFolder || *Version >= 3); + return FileChooserPortalVersion.has_value() + && (type != Type::ReadFolder || *FileChooserPortalVersion >= 3); } std::optional Get( diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h index 3c1cdc95a..f1ca9603a 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h @@ -15,6 +15,7 @@ namespace XDP { using Type = ::FileDialog::internal::Type; +void Start(); bool Use(Type type = Type::ReadFile); std::optional Get( QPointer parent, diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 339f61c5d..32b0a150d 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/linux/base_linux_dbus_utilities.h" #include "base/platform/linux/base_linux_xdp_utilities.h" #include "platform/linux/linux_notification_service_watcher.h" +#include "platform/linux/linux_xdp_file_dialog.h" #include "platform/linux/linux_mpris_support.h" #include "platform/linux/linux_gsd_media_keys.h" #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION @@ -964,6 +965,7 @@ void start() { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION NSWInstance = std::make_unique(); + FileDialog::XDP::Start(); #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION } From 5d0222b1c112677c767fc9951de2dd2b2a1006ba Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 1 May 2021 01:12:32 +0000 Subject: [PATCH 010/477] Update User-Agent for DNS to Chrome 90.0.4430.85. --- .../SourceFiles/mtproto/details/mtproto_domain_resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp index 8d6eb07ac..27e607222 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp @@ -65,7 +65,7 @@ QByteArray DnsUserAgent() { static const auto kResult = QByteArray( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/89.0.4389.90 Safari/537.36"); + "Chrome/90.0.4430.85 Safari/537.36"); return kResult; } From 34534a9653fd2308f7b04863f28bb71b741a1890 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 11 Mar 2021 13:11:46 +0400 Subject: [PATCH 011/477] Use kernel accelerated sendfile to copy files on Linux --- Telegram/SourceFiles/_other/updater_linux.cpp | 20 ++++++++++++---- .../platform/linux/specific_linux.cpp | 23 +++++++++++++++---- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index 5d4d742e2..911dab048 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -97,11 +97,6 @@ bool copyFile(const char *from, const char *to) { 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 @@ -110,6 +105,21 @@ bool copyFile(const char *from, const char *to) { fclose(fto); return false; } + + ssize_t copied = sendfile( + fileno(ffrom), + fileno(fto), + nullptr, + fst.st_size); + + if (copied == -1) { + static const int BufSize = 65536; + char buf[BufSize]; + while (size_t size = fread(buf, 1, BufSize, ffrom)) { + fwrite(buf, 1, size, fto); + } + } + //update to the same uid/gid if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { fclose(ffrom); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 32b0a150d..43e523f59 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -1022,11 +1022,6 @@ bool linuxMoveFile(const char *from, const char *to) { 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 @@ -1035,6 +1030,24 @@ bool linuxMoveFile(const char *from, const char *to) { fclose(fto); return false; } + + ssize_t copied = -1; +#ifdef Q_OS_LINUX + copied = sendfile( + fileno(ffrom), + fileno(fto), + nullptr, + fst.st_size); +#endif // Q_OS_LINUX + + if (copied == -1) { + static const int BufSize = 65536; + char buf[BufSize]; + while (size_t size = fread(buf, 1, BufSize, ffrom)) { + fwrite(buf, 1, size, fto); + } + } + //update to the same uid/gid if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { fclose(ffrom); From d87ea056c6166345d061fc3a69a6f95eceaab0a1 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 2 May 2021 10:30:47 +0400 Subject: [PATCH 012/477] Fix a crash in NotificationData::show --- .../linux/notifications_manager_linux.cpp | 166 ++++++++---------- 1 file changed, 72 insertions(+), 94 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 87b1a7059..ea2e0888a 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -46,9 +46,7 @@ bool InhibitionSupported = false; std::optional CurrentServerInformation; QStringList CurrentCapabilities; -void StartServiceAsync( - Fn callback, - const Glib::RefPtr &cancellable = Glib::RefPtr()) { +void StartServiceAsync(Fn callback) { try { const auto connection = Gio::DBus::Connection::get_sync( Gio::DBus::BusType::BUS_TYPE_SESSION); @@ -77,8 +75,7 @@ void StartServiceAsync( } crl::on_main([=] { callback(); }); - }, - cancellable); + }); return; } catch (...) { @@ -357,7 +354,7 @@ Glib::ustring GetImageKey(const QVersionNumber &specificationVersion) { return "icon_data"; } -class NotificationData : public sigc::trackable { +class NotificationData : public base::has_weak_ptr { public: using NotificationId = Window::Notifications::Manager::NotificationId; @@ -382,7 +379,6 @@ public: private: Glib::RefPtr _dbusConnection; - Glib::RefPtr _cancellable; base::weak_ptr _manager; Glib::ustring _title; @@ -397,21 +393,10 @@ private: uint _notificationClosedSignalId = 0; NotificationId _id; - void notificationShown( - const Glib::RefPtr &result); - void notificationClosed(uint id, uint reason); void actionInvoked(uint id, const Glib::ustring &actionName); void notificationReplied(uint id, const Glib::ustring &text); - void signalEmitted( - const Glib::RefPtr &connection, - const Glib::ustring &sender_name, - const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters); - }; using Notification = std::shared_ptr; @@ -423,8 +408,7 @@ NotificationData::NotificationData( const QString &msg, NotificationId id, bool hideReplyButton) -: _cancellable(Gio::Cancellable::create()) -, _manager(manager) +: _manager(manager) , _title(title.toStdString()) , _imageKey(GetImageKey(CurrentServerInformationValue().specVersion)) , _id(id) { @@ -440,6 +424,47 @@ NotificationData::NotificationData( const auto capabilities = CurrentCapabilities; + const auto signalEmitted = crl::guard(this, [=]( + const Glib::RefPtr &connection, + const Glib::ustring &sender_name, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &signal_name, + const Glib::VariantContainerBase ¶meters) { + try { + auto parametersCopy = parameters; + + if (signal_name == "ActionInvoked") { + const auto id = base::Platform::GlibVariantCast( + parametersCopy.get_child(0)); + + const auto actionName = base::Platform::GlibVariantCast< + Glib::ustring>(parametersCopy.get_child(1)); + + actionInvoked(id, actionName); + } else if (signal_name == "NotificationReplied") { + const auto id = base::Platform::GlibVariantCast( + parametersCopy.get_child(0)); + + const auto text = base::Platform::GlibVariantCast( + parametersCopy.get_child(1)); + + notificationReplied(id, text); + } else if (signal_name == "NotificationClosed") { + const auto id = base::Platform::GlibVariantCast( + parametersCopy.get_child(0)); + + const auto reason = base::Platform::GlibVariantCast( + parametersCopy.get_child(1)); + + notificationClosed(id, reason); + } + } catch (const std::exception &e) { + LOG(("Native Notification Error: %1").arg( + QString::fromStdString(e.what()))); + } + }); + if (capabilities.contains(qsl("body-markup"))) { _body = subtitle.isEmpty() ? msg.toHtmlEscaped().toStdString() @@ -468,7 +493,7 @@ NotificationData::NotificationData( tr::lng_notification_reply(tr::now).toStdString()); _notificationRepliedSignalId = _dbusConnection->signal_subscribe( - sigc::mem_fun(this, &NotificationData::signalEmitted), + signalEmitted, std::string(kService), std::string(kInterface), "NotificationReplied", @@ -481,7 +506,7 @@ NotificationData::NotificationData( } _actionInvokedSignalId = _dbusConnection->signal_subscribe( - sigc::mem_fun(this, &NotificationData::signalEmitted), + signalEmitted, std::string(kService), std::string(kInterface), "ActionInvoked", @@ -515,7 +540,7 @@ NotificationData::NotificationData( QGuiApplication::desktopFileName().chopped(8).toStdString()); _notificationClosedSignalId = _dbusConnection->signal_subscribe( - sigc::mem_fun(this, &NotificationData::signalEmitted), + signalEmitted, std::string(kService), std::string(kInterface), "NotificationClosed", @@ -523,10 +548,6 @@ NotificationData::NotificationData( } NotificationData::~NotificationData() { - if (_cancellable) { - _cancellable->cancel(); - } - if (_dbusConnection) { if (_actionInvokedSignalId != 0) { _dbusConnection->signal_unsubscribe(_actionInvokedSignalId); @@ -544,7 +565,7 @@ NotificationData::~NotificationData() { void NotificationData::show() { // a hack for snap's activation restriction - StartServiceAsync([=] { + StartServiceAsync(crl::guard(this, [=] { const auto iconName = _imageKey.empty() || _hints.find(_imageKey) == end(_hints) ? Glib::ustring(GetIconName().toStdString()) @@ -564,32 +585,30 @@ void NotificationData::show() { _hints, -1, }), - sigc::mem_fun(this, &NotificationData::notificationShown), + crl::guard(this, [=]( + const Glib::RefPtr &result) { + try { + auto reply = _dbusConnection->call_finish(result); + _notificationId = base::Platform::GlibVariantCast( + reply.get_child(0)); + + return; + } catch (const Glib::Error &e) { + LOG(("Native Notification Error: %1").arg( + QString::fromStdString(e.what()))); + } catch (const std::exception &e) { + LOG(("Native Notification Error: %1").arg( + QString::fromStdString(e.what()))); + } + + const auto manager = _manager; + const auto my = _id; + crl::on_main(manager, [=] { + manager->clearNotification(my); + }); + }), std::string(kService)); - }, _cancellable); -} - -void NotificationData::notificationShown( - const Glib::RefPtr &result) { - try { - auto reply = _dbusConnection->call_finish(result); - _notificationId = base::Platform::GlibVariantCast( - reply.get_child(0)); - - return; - } catch (const Glib::Error &e) { - LOG(("Native Notification Error: %1").arg( - QString::fromStdString(e.what()))); - } catch (const std::exception &e) { - LOG(("Native Notification Error: %1").arg( - QString::fromStdString(e.what()))); - } - - const auto manager = _manager; - const auto my = _id; - crl::on_main(manager, [=] { - manager->clearNotification(my); - }); + })); } void NotificationData::close() { @@ -625,47 +644,6 @@ void NotificationData::setImage(const QString &imagePath) { }); } -void NotificationData::signalEmitted( - const Glib::RefPtr &connection, - const Glib::ustring &sender_name, - const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters) { - try { - auto parametersCopy = parameters; - - if (signal_name == "ActionInvoked") { - const auto id = base::Platform::GlibVariantCast( - parametersCopy.get_child(0)); - - const auto actionName = base::Platform::GlibVariantCast< - Glib::ustring>(parametersCopy.get_child(1)); - - actionInvoked(id, actionName); - } else if (signal_name == "NotificationReplied") { - const auto id = base::Platform::GlibVariantCast( - parametersCopy.get_child(0)); - - const auto text = base::Platform::GlibVariantCast( - parametersCopy.get_child(1)); - - notificationReplied(id, text); - } else if (signal_name == "NotificationClosed") { - const auto id = base::Platform::GlibVariantCast( - parametersCopy.get_child(0)); - - const auto reason = base::Platform::GlibVariantCast( - parametersCopy.get_child(1)); - - notificationClosed(id, reason); - } - } catch (const std::exception &e) { - LOG(("Native Notification Error: %1").arg( - QString::fromStdString(e.what()))); - } -} - void NotificationData::notificationClosed(uint id, uint reason) { if (id == _notificationId) { const auto manager = _manager; From b1c95d719a0064f6872b09177734acbd5b1651c9 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 2 May 2021 13:36:27 +0400 Subject: [PATCH 013/477] Use crl::guard in XDPFileDialog --- .../platform/linux/linux_xdp_file_dialog.cpp | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp index ffb651993..dc65476e8 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp @@ -128,7 +128,7 @@ void ComputeFileChooserPortalVersion() { // // XDP file dialog is a dialog obtained via a DBus service // provided by the current desktop environment. -class XDPFileDialog : public QDialog, public sigc::trackable { +class XDPFileDialog : public QDialog { public: enum ConditionType : uint { GlobalPattern = 0, @@ -191,12 +191,8 @@ public: private: void openPortal(); void gotResponse( - const Glib::RefPtr &connection, - const Glib::ustring &sender_name, - const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters); + uint response, + const std::map &results); void showHelper( Qt::WindowFlags windowFlags, @@ -208,7 +204,6 @@ private: rpl::producer<> rejected(); Glib::RefPtr _dbusConnection; - Glib::RefPtr _cancellable; uint _requestSignalId = 0; // Options @@ -263,10 +258,6 @@ XDPFileDialog::XDPFileDialog( } XDPFileDialog::~XDPFileDialog() { - if (_cancellable) { - _cancellable->cancel(); - } - if (_dbusConnection && _requestSignalId != 0) { _dbusConnection->signal_unsubscribe(_requestSignalId); } @@ -419,15 +410,38 @@ void XDPFileDialog::openPortal() { + handleToken; _requestSignalId = _dbusConnection->signal_subscribe( - sigc::mem_fun(this, &XDPFileDialog::gotResponse), + crl::guard(this, [=]( + const Glib::RefPtr &connection, + const Glib::ustring &sender_name, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &signal_name, + const Glib::VariantContainerBase ¶meters) { + try { + auto parametersCopy = parameters; + + const auto response = base::Platform::GlibVariantCast( + parametersCopy.get_child(0)); + + const auto results = base::Platform::GlibVariantCast< + std::map< + Glib::ustring, + Glib::VariantBase + >>(parametersCopy.get_child(1)); + + gotResponse(response, results); + } catch (const std::exception &e) { + LOG(("XDP File Dialog Error: %1").arg( + QString::fromStdString(e.what()))); + + _reject.fire({}); + } + }), {}, "org.freedesktop.portal.Request", "Response", requestPath); - // synchronize functor deletion by this cancellable - _cancellable = Gio::Cancellable::create(); - _dbusConnection->call( std::string(kXDGDesktopPortalObjectPath), std::string(kXDGDesktopPortalFileChooserInterface), @@ -439,7 +453,7 @@ void XDPFileDialog::openPortal() { _windowTitle, options, }), - [=](const Glib::RefPtr &result) { + crl::guard(this, [=](const Glib::RefPtr &result) { try { auto reply = _dbusConnection->call_finish(result); @@ -478,8 +492,7 @@ void XDPFileDialog::openPortal() { _reject.fire({}); }); } - }, - _cancellable, + }), std::string(kXDGDesktopPortalService)); } catch (...) { _failedToOpen = true; @@ -615,24 +628,9 @@ void XDPFileDialog::showHelper( } void XDPFileDialog::gotResponse( - const Glib::RefPtr &connection, - const Glib::ustring &sender_name, - const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters) { + uint response, + const std::map &results) { try { - auto parametersCopy = parameters; - - const auto response = base::Platform::GlibVariantCast( - parametersCopy.get_child(0)); - - const auto results = base::Platform::GlibVariantCast< - std::map< - Glib::ustring, - Glib::VariantBase - >>(parametersCopy.get_child(1)); - if (!response) { if (const auto i = results.find("uris"); i != end(results)) { _selectedFiles = base::Platform::GlibVariantCast< From 56a5363eb96211d35238fc7e52a745e2c563b111 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 2 May 2021 13:36:57 +0400 Subject: [PATCH 014/477] Move keyPressed out of GSDMediaKeys::Private --- .../platform/linux/linux_gsd_media_keys.cpp | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.cpp b/Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.cpp index 4151736de..c5f19e03f 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gsd_media_keys.cpp @@ -27,28 +27,7 @@ constexpr auto kMATEObjectPath = "/org/mate/SettingsDaemon/MediaKeys"_cs; constexpr auto kInterface = kService; constexpr auto kMATEInterface = "org.mate.SettingsDaemon.MediaKeys"_cs; -} // namespace - -class GSDMediaKeys::Private : public sigc::trackable { -public: - Glib::RefPtr dbusConnection; - - Glib::ustring service; - Glib::ustring objectPath; - Glib::ustring interface; - uint signalId = 0; - bool grabbed = false; - - void keyPressed( - const Glib::RefPtr &connection, - const Glib::ustring &sender_name, - const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters); -}; - -void GSDMediaKeys::Private::keyPressed( +void KeyPressed( const Glib::RefPtr &connection, const Glib::ustring &sender_name, const Glib::ustring &object_path, @@ -83,6 +62,19 @@ void GSDMediaKeys::Private::keyPressed( } } +} // namespace + +class GSDMediaKeys::Private { +public: + Glib::RefPtr dbusConnection; + + Glib::ustring service; + Glib::ustring objectPath; + Glib::ustring interface; + uint signalId = 0; + bool grabbed = false; +}; + GSDMediaKeys::GSDMediaKeys() : _private(std::make_unique()) { try { @@ -126,7 +118,7 @@ GSDMediaKeys::GSDMediaKeys() _private->grabbed = true; _private->signalId = _private->dbusConnection->signal_subscribe( - sigc::mem_fun(_private.get(), &Private::keyPressed), + sigc::ptr_fun(KeyPressed), _private->service, _private->interface, "MediaPlayerKeyPressed", From 99b9a46428ec8be6cce7f84b7b9d8bc01a268796 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 2 May 2021 13:48:49 +0400 Subject: [PATCH 015/477] Update lib_base --- Telegram/lib_base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/lib_base b/Telegram/lib_base index d01871440..37f38c039 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit d0187144060eb6689c0bfd5ddd44854561e929c6 +Subproject commit 37f38c039f69d80762d663b6c7e83deff1f87935 From 3cad89f299e33a31ef9d85d4216f7a9051fb442c Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 11:21:07 +0400 Subject: [PATCH 016/477] Fix build, add logs for sendfile(). --- Telegram/SourceFiles/_other/updater_linux.cpp | 13 +++++++++++++ .../SourceFiles/platform/linux/specific_linux.cpp | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index 911dab048..1a634adeb 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include #include #include #include @@ -113,11 +114,23 @@ bool copyFile(const char *from, const char *to) { fst.st_size); if (copied == -1) { + writeLog( + "Copy by sendfile '%s' to '%s' failed, error: %d, fallback now.", + from, + to, + int(errno)); static const int BufSize = 65536; char buf[BufSize]; while (size_t size = fread(buf, 1, BufSize, ffrom)) { fwrite(buf, 1, size, fto); } + } else { + writeLog( + "Copy by sendfile '%s' to '%s' done, size: %d, result: %d.", + from, + to, + int(fst.st_size), + int(copied)); } //update to the same uid/gid diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 43e523f59..3baf7c1dc 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include #include #include #include @@ -1039,13 +1040,25 @@ bool linuxMoveFile(const char *from, const char *to) { nullptr, fst.st_size); #endif // Q_OS_LINUX - + if (copied == -1) { + DEBUG_LOG(("Update Error: " + "Copy by sendfile '%1' to '%2' failed, error: %3, fallback now." + ).arg(from + ).arg(to + ).arg(errno)); static const int BufSize = 65536; char buf[BufSize]; while (size_t size = fread(buf, 1, BufSize, ffrom)) { fwrite(buf, 1, size, fto); } + } else { + DEBUG_LOG(("Update Info: " + "Copy by sendfile '%1' to '%2' done, size: %3, result: %4." + ).arg(from + ).arg(to + ).arg(fst.st_size + ).arg(copied)); } //update to the same uid/gid From 197b3c1cb533f7eacef4ea4e3f3e3d2ed6fe615d Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 7 Oct 2020 12:00:31 +0300 Subject: [PATCH 017/477] Push all QSaveFile-s to background thread. --- Telegram/SourceFiles/intro/intro_step.cpp | 2 + .../details/storage_file_utilities.cpp | 306 ++++++++++++++---- .../storage/details/storage_file_utilities.h | 15 +- Telegram/SourceFiles/storage/localstorage.cpp | 5 + Telegram/SourceFiles/storage/localstorage.h | 1 + .../SourceFiles/storage/storage_account.cpp | 2 + 6 files changed, 255 insertions(+), 76 deletions(-) diff --git a/Telegram/SourceFiles/intro/intro_step.cpp b/Telegram/SourceFiles/intro/intro_step.cpp index 3b4535671..7606c30ef 100644 --- a/Telegram/SourceFiles/intro/intro_step.cpp +++ b/Telegram/SourceFiles/intro/intro_step.cpp @@ -157,6 +157,7 @@ void Step::finish(const MTPUser &user, QImage &&photo) { _account->logOut(); crl::on_main(raw, [=] { Core::App().domain().activate(raw); + Local::sync(); }); return; } @@ -203,6 +204,7 @@ void Step::createSession( if (session.supportMode()) { PrepareSupportMode(&session); } + Local::sync(); } void Step::paintEvent(QPaintEvent *e) { diff --git a/Telegram/SourceFiles/storage/details/storage_file_utilities.cpp b/Telegram/SourceFiles/storage/details/storage_file_utilities.cpp index d859d3c3d..a59d310a2 100644 --- a/Telegram/SourceFiles/storage/details/storage_file_utilities.cpp +++ b/Telegram/SourceFiles/storage/details/storage_file_utilities.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_file_utilities.h" #include "base/openssl_help.h" +#include #include #include @@ -23,6 +24,217 @@ constexpr auto TdfMagicLen = int(sizeof(TdfMagic)); constexpr auto kStrongIterationsCount = 100'000; +struct WriteEntry { + QString basePath; + QString base; + QByteArray data; + QByteArray md5; +}; + +class WriteManager final { +public: + explicit WriteManager(crl::weak_on_thread weak); + + void write(WriteEntry &&entry); + void writeSync(WriteEntry &&entry); + void writeSyncAll(); + +private: + void scheduleWrite(); + void writeScheduled(); + bool writeOneScheduledNow(); + void writeNow(WriteEntry &&entry); + + template + [[nodiscard]] bool open(File &file, const WriteEntry &entry, char postfix); + + [[nodiscard]] QString path(const WriteEntry &entry, char postfix) const; + [[nodiscard]] bool writeHeader( + const QString &basePath, + QFileDevice &file); + + crl::weak_on_thread _weak; + std::deque _scheduled; + +}; + +class AsyncWriteManager final { +public: + void write(WriteEntry &&entry); + void writeSync(WriteEntry &&entry); + void sync(); + void stop(); + +private: + std::optional> _manager; + bool _finished = false; + +}; + +WriteManager::WriteManager(crl::weak_on_thread weak) +: _weak(std::move(weak)) { +} + +void WriteManager::write(WriteEntry &&entry) { + const auto i = ranges::find(_scheduled, entry.base, &WriteEntry::base); + if (i == end(_scheduled)) { + _scheduled.push_back(std::move(entry)); + } else { + *i = std::move(entry); + } + scheduleWrite(); +} + +void WriteManager::writeSync(WriteEntry &&entry) { + const auto i = ranges::find(_scheduled, entry.base, &WriteEntry::base); + if (i != end(_scheduled)) { + _scheduled.erase(i); + } + writeNow(std::move(entry)); +} + +void WriteManager::writeNow(WriteEntry &&entry) { + const auto path = [&](char postfix) { + return this->path(entry, postfix); + }; + const auto open = [&](auto &file, char postfix) { + return this->open(file, entry, postfix); + }; + const auto write = [&](auto &file) { + file.write(entry.data); + file.write(entry.md5); + }; + const auto safe = path('s'); + const auto simple = path('0'); + const auto backup = path('1'); + QSaveFile save; + if (open(save, 's')) { + write(save); + if (save.commit()) { + QFile::remove(simple); + QFile::remove(backup); + return; + } + LOG(("Storage Error: Could not commit '%1'.").arg(safe)); + } + QFile plain; + if (open(plain, '0')) { + write(plain); + base::Platform::FlushFileData(plain); + plain.close(); + + QFile::remove(backup); + if (base::Platform::RenameWithOverwrite(simple, safe)) { + return; + } + QFile::remove(safe); + LOG(("Storage Error: Could not rename '%1' to '%2', removing.").arg( + simple, + safe)); + } +} + +void WriteManager::writeSyncAll() { + while (writeOneScheduledNow()) { + } +} + +bool WriteManager::writeOneScheduledNow() { + if (_scheduled.empty()) { + return false; + } + + auto entry = std::move(_scheduled.front()); + _scheduled.pop_front(); + + writeNow(std::move(entry)); + return true; +} + +bool WriteManager::writeHeader(const QString &basePath, QFileDevice &file) { + if (!file.open(QIODevice::WriteOnly)) { + const auto dir = QDir(basePath); + if (dir.exists()) { + return false; + } else if (!QDir().mkpath(dir.absolutePath())) { + return false; + } else if (!file.open(QIODevice::WriteOnly)) { + return false; + } + } + file.write(TdfMagic, TdfMagicLen); + const auto version = qint32(AppVersion); + file.write((const char*)&version, sizeof(version)); + return true; +} + +QString WriteManager::path(const WriteEntry &entry, char postfix) const { + return entry.base + postfix; +} + +template +bool WriteManager::open(File &file, const WriteEntry &entry, char postfix) { + const auto name = path(entry, postfix); + file.setFileName(name); + if (!writeHeader(entry.basePath, file)) { + LOG(("Storage Error: Could not open '%1' for writing.").arg(name)); + return false; + } + return true; +} + +void WriteManager::scheduleWrite() { + _weak.with([](WriteManager &that) { + that.writeScheduled(); + }); +} + +void WriteManager::writeScheduled() { + if (writeOneScheduledNow() && !_scheduled.empty()) { + scheduleWrite(); + } +} + +void AsyncWriteManager::write(WriteEntry &&entry) { + Expects(!_finished); + + if (!_manager) { + _manager.emplace(); + } + _manager->with([entry = std::move(entry)](WriteManager &manager) mutable { + manager.write(std::move(entry)); + }); +} + +void AsyncWriteManager::writeSync(WriteEntry &&entry) { + Expects(!_finished); + + if (!_manager) { + _manager.emplace(); + } + _manager->with_sync([&](WriteManager &manager) { + manager.writeSync(std::move(entry)); + }); +} + +void AsyncWriteManager::sync() { + if (_manager) { + _manager->with_sync([](WriteManager &manager) { + manager.writeSyncAll(); + }); + } +} + +void AsyncWriteManager::stop() { + if (_manager) { + sync(); + _manager.reset(); + } + _finished = true; +} + +AsyncWriteManager Manager; + } // namespace QString ToFilePart(FileKey val) { @@ -165,14 +377,17 @@ void EncryptedDescriptor::finish() { FileWriteDescriptor::FileWriteDescriptor( const FileKey &key, - const QString &basePath) -: FileWriteDescriptor(ToFilePart(key), basePath) { + const QString &basePath, + bool sync) +: FileWriteDescriptor(ToFilePart(key), basePath, sync) { } FileWriteDescriptor::FileWriteDescriptor( const QString &name, - const QString &basePath) -: _basePath(basePath) { + const QString &basePath, + bool sync) +: _basePath(basePath) +, _sync(sync) { init(name); } @@ -180,42 +395,6 @@ FileWriteDescriptor::~FileWriteDescriptor() { finish(); } -QString FileWriteDescriptor::path(char postfix) const { - return _base + postfix; -} - -template -bool FileWriteDescriptor::open(File &file, char postfix) { - const auto name = path(postfix); - file.setFileName(name); - if (!writeHeader(file)) { - LOG(("Storage Error: Could not open '%1' for writing.").arg(name)); - return false; - } - return true; -} - -bool FileWriteDescriptor::writeHeader(QFileDevice &file) { - if (!file.open(QIODevice::WriteOnly)) { - const auto dir = QDir(_basePath); - if (dir.exists()) { - return false; - } else if (!QDir().mkpath(dir.absolutePath())) { - return false; - } else if (!file.open(QIODevice::WriteOnly)) { - return false; - } - } - file.write(TdfMagic, TdfMagicLen); - const auto version = qint32(AppVersion); - file.write((const char*)&version, sizeof(version)); - return true; -} - -void FileWriteDescriptor::writeFooter(QFileDevice &file) { - file.write((const char*)_md5.result(), 0x10); -} - void FileWriteDescriptor::init(const QString &name) { _base = _basePath + name; _buffer.setBuffer(&_safeData); @@ -257,35 +436,16 @@ void FileWriteDescriptor::finish() { _buffer.close(); - const auto safe = path('s'); - const auto simple = path('0'); - const auto backup = path('1'); - QSaveFile save; - if (open(save, 's')) { - save.write(_safeData); - writeFooter(save); - if (save.commit()) { - QFile::remove(simple); - QFile::remove(backup); - return; - } - LOG(("Storage Error: Could not commit '%1'.").arg(safe)); - } - QFile plain; - if (open(plain, '0')) { - plain.write(_safeData); - writeFooter(plain); - base::Platform::FlushFileData(plain); - plain.close(); - - QFile::remove(backup); - if (base::Platform::RenameWithOverwrite(simple, safe)) { - return; - } - QFile::remove(safe); - LOG(("Storage Error: Could not rename '%1' to '%2', removing.").arg( - simple, - safe)); + auto entry = WriteEntry{ + .basePath = _basePath, + .base = _base, + .data = _safeData, + .md5 = QByteArray((const char*)_md5.result(), 0x10) + }; + if (_sync) { + Manager.writeSync(std::move(entry)); + } else { + Manager.write(std::move(entry)); } } @@ -504,5 +664,13 @@ bool ReadEncryptedFile( return ReadEncryptedFile(result, ToFilePart(fkey), basePath, key); } +void Sync() { + Manager.sync(); +} + +void Finish() { + Manager.stop(); +} + } // namespace details } // namespace Storage diff --git a/Telegram/SourceFiles/storage/details/storage_file_utilities.h b/Telegram/SourceFiles/storage/details/storage_file_utilities.h index dcc12ac3b..f854c48ac 100644 --- a/Telegram/SourceFiles/storage/details/storage_file_utilities.h +++ b/Telegram/SourceFiles/storage/details/storage_file_utilities.h @@ -56,10 +56,12 @@ class FileWriteDescriptor final { public: FileWriteDescriptor( const FileKey &key, - const QString &basePath); + const QString &basePath, + bool sync = false); FileWriteDescriptor( const QString &name, - const QString &basePath); + const QString &basePath, + bool sync = false); ~FileWriteDescriptor(); void writeData(const QByteArray &data); @@ -69,11 +71,6 @@ public: private: void init(const QString &name); - [[nodiscard]] QString path(char postfix) const; - template - [[nodiscard]] bool open(File &file, char postfix); - [[nodiscard]] bool writeHeader(QFileDevice &file); - void writeFooter(QFileDevice &file); void finish(); const QString _basePath; @@ -83,6 +80,7 @@ private: QString _base; HashMd5 _md5; int _fullSize = 0; + bool _sync = false; }; @@ -108,5 +106,8 @@ bool ReadEncryptedFile( const QString &basePath, const MTP::AuthKeyPtr &key); +void Sync(); +void Finish(); + } // namespace details } // namespace Storage diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 68ec90471..bdb62effb 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -338,8 +338,13 @@ bool _readOldMtpData(bool remove, ReadSettingsContext &context) { } // namespace +void sync() { + Storage::details::Sync(); +} + void finish() { delete base::take(_localLoader); + Storage::details::Finish(); } void InitialLoadTheme(); diff --git a/Telegram/SourceFiles/storage/localstorage.h b/Telegram/SourceFiles/storage/localstorage.h index 87417776c..d79314bcb 100644 --- a/Telegram/SourceFiles/storage/localstorage.h +++ b/Telegram/SourceFiles/storage/localstorage.h @@ -49,6 +49,7 @@ using AuthKeyPtr = std::shared_ptr; namespace Local { void start(); +void sync(); void finish(); void writeSettings(); diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 91ed60c16..f77f9b8a1 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -577,6 +577,8 @@ void Account::reset() { QDir(LegacyTempDirectory()).removeRecursively(); QDir(temp).removeRecursively(); }); + + Local::sync(); } void Account::writeLocations() { From 298215542ee41c2097da53aa0b6b00b7ea927bc3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 12:29:21 +0400 Subject: [PATCH 018/477] Fix legacy-style connect in Intro. --- Telegram/SourceFiles/intro/intro_phone.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/intro/intro_phone.cpp b/Telegram/SourceFiles/intro/intro_phone.cpp index d4260e460..54a7c2cfe 100644 --- a/Telegram/SourceFiles/intro/intro_phone.cpp +++ b/Telegram/SourceFiles/intro/intro_phone.cpp @@ -53,13 +53,13 @@ PhoneWidget::PhoneWidget( connect(_country, &CountryInput::codeChanged, [=](const QString &code) { _code->codeSelected(code); + _phone->chooseCode(code); }); _code->codeChanged( ) | rpl::start_with_next([=](const QString &code) { _country->onChooseCode(code); _phone->chooseCode(code); }, _code->lifetime()); - connect(_country, SIGNAL(codeChanged(const QString &)), _phone, SLOT(onChooseCode(const QString &))); _code->addedToNumber( ) | rpl::start_with_next([=](const QString &added) { _phone->addedToNumber(added); From 434a4af9ef4a46691c00a89e80b974eff3c72882 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 12:43:23 +0400 Subject: [PATCH 019/477] Fix sendfile() arguments. --- Telegram/SourceFiles/_other/updater_linux.cpp | 2 +- Telegram/SourceFiles/platform/linux/specific_linux.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index 1a634adeb..ce4da02be 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -108,8 +108,8 @@ bool copyFile(const char *from, const char *to) { } ssize_t copied = sendfile( - fileno(ffrom), fileno(fto), + fileno(ffrom), nullptr, fst.st_size); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 3baf7c1dc..ea14312b9 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -1035,8 +1035,8 @@ bool linuxMoveFile(const char *from, const char *to) { ssize_t copied = -1; #ifdef Q_OS_LINUX copied = sendfile( - fileno(ffrom), fileno(fto), + fileno(ffrom), nullptr, fst.st_size); #endif // Q_OS_LINUX From 98180d3a9e994081a134196adad0023651d28bf3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 13:08:50 +0400 Subject: [PATCH 020/477] Always guard and send on_main in native notifications. --- .../linux/notifications_manager_linux.cpp | 221 +++++++++--------- 1 file changed, 110 insertions(+), 111 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index ea2e0888a..399b8d799 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -34,6 +34,8 @@ constexpr auto kObjectPath = "/org/freedesktop/Notifications"_cs; constexpr auto kInterface = kService; constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs; +using namespace base::Platform; + struct ServerInformation { QString name; QString vendor; @@ -51,10 +53,10 @@ void StartServiceAsync(Fn callback) { const auto connection = Gio::DBus::Connection::get_sync( Gio::DBus::BusType::BUS_TYPE_SESSION); - base::Platform::DBus::StartServiceByNameAsync( + DBus::StartServiceByNameAsync( connection, std::string(kService), - [=](Fn result) { + [=](Fn result) { try { result(); // get the error if any } catch (const Glib::Error &e) { @@ -74,14 +76,14 @@ void StartServiceAsync(Fn callback) { QString::fromStdString(e.what()))); } - crl::on_main([=] { callback(); }); + crl::on_main(callback); }); return; } catch (...) { } - crl::on_main([=] { callback(); }); + crl::on_main(callback); } bool GetServiceRegistered() { @@ -91,7 +93,7 @@ bool GetServiceRegistered() { const auto hasOwner = [&] { try { - return base::Platform::DBus::NameHasOwner( + return DBus::NameHasOwner( connection, std::string(kService)); } catch (...) { @@ -102,7 +104,7 @@ bool GetServiceRegistered() { static const auto activatable = [&] { try { return ranges::contains( - base::Platform::DBus::ListActivatableNames(connection), + DBus::ListActivatableNames(connection), Glib::ustring(std::string(kService))); } catch (...) { return false; @@ -131,17 +133,17 @@ void GetServerInformation( try { auto reply = connection->call_finish(result); - const auto name = base::Platform::GlibVariantCast< - Glib::ustring>(reply.get_child(0)); + const auto name = GlibVariantCast( + reply.get_child(0)); - const auto vendor = base::Platform::GlibVariantCast< - Glib::ustring>(reply.get_child(1)); + const auto vendor = GlibVariantCast( + reply.get_child(1)); - const auto version = base::Platform::GlibVariantCast< - Glib::ustring>(reply.get_child(2)); + const auto version = GlibVariantCast( + reply.get_child(2)); - const auto specVersion = base::Platform::GlibVariantCast< - Glib::ustring>(reply.get_child(3)); + const auto specVersion = GlibVariantCast( + reply.get_child(3)); crl::on_main([=] { callback(ServerInformation{ @@ -192,8 +194,8 @@ void GetCapabilities(Fn callback) { QStringList value; ranges::transform( - base::Platform::GlibVariantCast< - std::vector>(reply.get_child(0)), + GlibVariantCast>( + reply.get_child(0)), ranges::back_inserter(value), QString::fromStdString); @@ -232,7 +234,7 @@ void GetInhibitionSupported(Fn callback) { std::string(kObjectPath), std::string(kPropertiesInterface), "Get", - base::Platform::MakeGlibVariant(std::tuple{ + MakeGlibVariant(std::tuple{ Glib::ustring(std::string(kInterface)), Glib::ustring("Inhibited"), }), @@ -283,7 +285,7 @@ bool Inhibited() { Gio::DBus::BusType::BUS_TYPE_SESSION); // a hack for snap's activation restriction - base::Platform::DBus::StartServiceByName( + DBus::StartServiceByName( connection, std::string(kService)); @@ -291,15 +293,14 @@ bool Inhibited() { std::string(kObjectPath), std::string(kPropertiesInterface), "Get", - base::Platform::MakeGlibVariant(std::tuple{ + MakeGlibVariant(std::tuple{ Glib::ustring(std::string(kInterface)), Glib::ustring("Inhibited"), }), std::string(kService)); - return base::Platform::GlibVariantCast( - base::Platform::GlibVariantCast( - reply.get_child(0))); + return GlibVariantCast( + GlibVariantCast(reply.get_child(0))); } catch (const Glib::Error &e) { LOG(("Native Notification Error: %1").arg( QString::fromStdString(e.what()))); @@ -354,16 +355,18 @@ Glib::ustring GetImageKey(const QVersionNumber &specificationVersion) { return "icon_data"; } -class NotificationData : public base::has_weak_ptr { +class NotificationData final : public base::has_weak_ptr { public: using NotificationId = Window::Notifications::Manager::NotificationId; NotificationData( - const base::weak_ptr &manager, + not_null manager, + NotificationId id); + + [[nodiscard]] bool init( const QString &title, const QString &subtitle, const QString &msg, - NotificationId id, bool hideReplyButton); NotificationData(const NotificationData &other) = delete; @@ -378,9 +381,10 @@ public: void setImage(const QString &imagePath); private: - Glib::RefPtr _dbusConnection; - base::weak_ptr _manager; + const not_null _manager; + NotificationId _id; + Glib::RefPtr _dbusConnection; Glib::ustring _title; Glib::ustring _body; std::vector _actions; @@ -391,7 +395,6 @@ private: uint _actionInvokedSignalId = 0; uint _notificationRepliedSignalId = 0; uint _notificationClosedSignalId = 0; - NotificationId _id; void notificationClosed(uint id, uint reason); void actionInvoked(uint id, const Glib::ustring &actionName); @@ -399,71 +402,73 @@ private: }; -using Notification = std::shared_ptr; +using Notification = std::unique_ptr; NotificationData::NotificationData( - const base::weak_ptr &manager, - const QString &title, - const QString &subtitle, - const QString &msg, - NotificationId id, - bool hideReplyButton) + not_null manager, + NotificationId id) : _manager(manager) -, _title(title.toStdString()) -, _imageKey(GetImageKey(CurrentServerInformationValue().specVersion)) , _id(id) { +} + +bool NotificationData::init( + const QString &title, + const QString &subtitle, + const QString &msg, + bool hideReplyButton) { try { _dbusConnection = Gio::DBus::Connection::get_sync( Gio::DBus::BusType::BUS_TYPE_SESSION); } catch (const Glib::Error &e) { LOG(("Native Notification Error: %1").arg( QString::fromStdString(e.what()))); - - return; + return false; } + const auto weak = base::make_weak(this); const auto capabilities = CurrentCapabilities; - const auto signalEmitted = crl::guard(this, [=]( - const Glib::RefPtr &connection, - const Glib::ustring &sender_name, - const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters) { + const auto signalEmitted = [=]( + const Glib::RefPtr &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; - if (signal_name == "ActionInvoked") { - const auto id = base::Platform::GlibVariantCast( - parametersCopy.get_child(0)); + const auto id = GlibVariantCast( + parameters.get_child(0)); - const auto actionName = base::Platform::GlibVariantCast< - Glib::ustring>(parametersCopy.get_child(1)); + const auto actionName = GlibVariantCast( + parameters.get_child(1)); - actionInvoked(id, actionName); + crl::on_main(weak, [=] { actionInvoked(id, actionName); }); } else if (signal_name == "NotificationReplied") { - const auto id = base::Platform::GlibVariantCast( - parametersCopy.get_child(0)); + const auto id = GlibVariantCast( + parameters.get_child(0)); - const auto text = base::Platform::GlibVariantCast( - parametersCopy.get_child(1)); + const auto text = GlibVariantCast( + parameters.get_child(1)); - notificationReplied(id, text); + crl::on_main(weak, [=] { notificationReplied(id, text); }); } else if (signal_name == "NotificationClosed") { - const auto id = base::Platform::GlibVariantCast( - parametersCopy.get_child(0)); + const auto id = GlibVariantCast( + parameters.get_child(0)); - const auto reason = base::Platform::GlibVariantCast( - parametersCopy.get_child(1)); + const auto reason = GlibVariantCast( + parameters.get_child(1)); - notificationClosed(id, reason); + crl::on_main(weak, [=] { notificationClosed(id, reason); }); } } catch (const std::exception &e) { LOG(("Native Notification Error: %1").arg( QString::fromStdString(e.what()))); } - }); + }; + + _title = title.toStdString(); + _imageKey = GetImageKey(CurrentServerInformationValue().specVersion); if (capabilities.contains(qsl("body-markup"))) { _body = subtitle.isEmpty() @@ -545,6 +550,7 @@ NotificationData::NotificationData( std::string(kInterface), "NotificationClosed", std::string(kObjectPath)); + return true; } NotificationData::~NotificationData() { @@ -565,7 +571,8 @@ NotificationData::~NotificationData() { void NotificationData::show() { // a hack for snap's activation restriction - StartServiceAsync(crl::guard(this, [=] { + const auto weak = base::make_weak(this); + StartServiceAsync(crl::guard(weak, [=] { const auto iconName = _imageKey.empty() || _hints.find(_imageKey) == end(_hints) ? Glib::ustring(GetIconName().toStdString()) @@ -575,7 +582,7 @@ void NotificationData::show() { std::string(kObjectPath), std::string(kInterface), "Notify", - base::Platform::MakeGlibVariant(std::tuple{ + MakeGlibVariant(std::tuple{ Glib::ustring(std::string(AppName)), uint(0), iconName, @@ -585,13 +592,14 @@ void NotificationData::show() { _hints, -1, }), - crl::guard(this, [=]( - const Glib::RefPtr &result) { + [=](const Glib::RefPtr &result) { try { auto reply = _dbusConnection->call_finish(result); - _notificationId = base::Platform::GlibVariantCast( + const auto notificationId = GlibVariantCast( reply.get_child(0)); - + crl::on_main(weak, [=] { + _notificationId = notificationId; + }); return; } catch (const Glib::Error &e) { LOG(("Native Notification Error: %1").arg( @@ -600,13 +608,10 @@ void NotificationData::show() { LOG(("Native Notification Error: %1").arg( QString::fromStdString(e.what()))); } - - const auto manager = _manager; - const auto my = _id; - crl::on_main(manager, [=] { - manager->clearNotification(my); + crl::on_main(weak, [=] { + _manager->clearNotification(_id); }); - }), + }, std::string(kService)); })); } @@ -616,11 +621,12 @@ void NotificationData::close() { std::string(kObjectPath), std::string(kInterface), "CloseNotification", - base::Platform::MakeGlibVariant(std::tuple{ + MakeGlibVariant(std::tuple{ _notificationId, }), {}, std::string(kService)); + _manager->clearNotification(_id); } void NotificationData::setImage(const QString &imagePath) { @@ -631,7 +637,7 @@ void NotificationData::setImage(const QString &imagePath) { const auto image = QImage(imagePath) .convertToFormat(QImage::Format_RGBA8888); - _hints[_imageKey] = base::Platform::MakeGlibVariant(std::tuple{ + _hints[_imageKey] = MakeGlibVariant(std::tuple{ image.width(), image.height(), image.bytesPerLine(), @@ -646,11 +652,7 @@ void NotificationData::setImage(const QString &imagePath) { void NotificationData::notificationClosed(uint id, uint reason) { if (id == _notificationId) { - const auto manager = _manager; - const auto my = _id; - crl::on_main(manager, [=] { - manager->clearNotification(my); - }); + _manager->clearNotification(_id); } } @@ -663,17 +665,9 @@ void NotificationData::actionInvoked( if (actionName == "default" || actionName == "mail-reply-sender") { - const auto manager = _manager; - const auto my = _id; - crl::on_main(manager, [=] { - manager->notificationActivated(my); - }); + _manager->notificationActivated(_id); } else if (actionName == "mail-mark-read") { - const auto manager = _manager; - const auto my = _id; - crl::on_main(manager, [=] { - manager->notificationReplied(my, {}); - }); + _manager->notificationReplied(_id, {}); } } @@ -681,13 +675,9 @@ void NotificationData::notificationReplied( uint id, const Glib::ustring &text) { if (id == _notificationId) { - const auto manager = _manager; - const auto my = _id; - crl::on_main(manager, [=] { - manager->notificationReplied( - my, - { QString::fromStdString(text), {} }); - }); + _manager->notificationReplied( + _id, + { QString::fromStdString(text), {} }); } } @@ -801,17 +791,19 @@ public: ~Private(); private: + const not_null _manager; + base::flat_map< FullPeer, base::flat_map> _notifications; Window::Notifications::CachedUserpics _cachedUserpics; - base::weak_ptr _manager; + }; Manager::Private::Private(not_null manager, Type type) -: _cachedUserpics(type) -, _manager(manager) { +: _manager(manager) +, _cachedUserpics(type) { if (!Supported()) { return; } @@ -856,13 +848,18 @@ void Manager::Private::showNotification( .sessionId = peer->session().uniqueId(), .peerId = peer->id }; - auto notification = std::make_shared( + const auto notificationId = NotificationId{ .full = key, .msgId = msgId }; + auto notification = std::make_unique( _manager, + notificationId); + const auto inited = notification->init( title, subtitle, msg, - NotificationId{ .full = key, .msgId = msgId }, hideReplyButton); + if (!inited) { + return; + } if (!hideNameAndPhoto) { const auto userpicKey = peer->userpicUniqueKey(userpicView); @@ -871,22 +868,24 @@ void Manager::Private::showNotification( } auto i = _notifications.find(key); - if (i != _notifications.cend()) { + if (i != end(_notifications)) { auto j = i->second.find(msgId); - if (j != i->second.end()) { - auto oldNotification = j->second; + if (j != end(i->second)) { + auto oldNotification = std::move(j->second); i->second.erase(j); oldNotification->close(); i = _notifications.find(key); } } - if (i == _notifications.cend()) { + if (i == end(_notifications)) { i = _notifications.emplace( key, base::flat_map()).first; } - i->second.emplace(msgId, notification); - notification->show(); + const auto j = i->second.emplace( + msgId, + std::move(notification)).first; + j->second->show(); } void Manager::Private::clearAll() { From 4fae827f1e7bc5cd557193690c42192e4b39c0a8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 13:23:28 +0400 Subject: [PATCH 021/477] Use a separate string for voice chat ending in groups. --- Telegram/Resources/langs/lang.strings | 1 + .../SourceFiles/history/history_service.cpp | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d7f614985..234782058 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1112,6 +1112,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_group_call_scheduled_group" = "{from} scheduled a voice chat for {date}"; "lng_action_group_call_scheduled_channel" = "Voice chat scheduled for {date}"; "lng_action_group_call_finished" = "Voice chat finished ({duration})"; +"lng_action_group_call_finished_group" = "{from} ended the voice chat ({duration})"; "lng_action_add_user" = "{from} added {user}"; "lng_action_add_users_many" = "{from} added {users}"; "lng_action_add_users_and_one" = "{accumulated}, {user}"; diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 34c23db8d..962126cea 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -338,6 +338,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { }; auto prepareGroupCall = [this](const MTPDmessageActionGroupCall &action) { + auto result = PreparedText{}; if (const auto duration = action.vduration()) { const auto seconds = duration->v; const auto days = seconds / 86400; @@ -350,9 +351,22 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { : (minutes > 1) ? tr::lng_group_call_duration_minutes(tr::now, lt_count, minutes) : tr::lng_group_call_duration_seconds(tr::now, lt_count, seconds); - return PreparedText{ tr::lng_action_group_call_finished(tr::now, lt_duration, text) }; + if (history()->peer->isBroadcast()) { + result.text = tr::lng_action_group_call_finished( + tr::now, + lt_duration, + text); + } else { + result.links.push_back(fromLink()); + result.text = tr::lng_action_group_call_finished_group( + tr::now, + lt_from, + fromLinkText(), + lt_duration, + text); + } + return result; } - auto result = PreparedText{}; if (history()->peer->isBroadcast()) { result.text = tr::lng_action_group_call_started_channel(tr::now); } else { From 578833446dcd8104046c2147cc46944dbe05e8c2 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 27 Apr 2021 16:16:20 +0400 Subject: [PATCH 022/477] Add support for write-protected update on Linux --- Telegram/SourceFiles/_other/updater_linux.cpp | 36 +++++++++++++++---- Telegram/SourceFiles/core/update_checker.cpp | 24 +++++++++++-- .../platform/linux/launcher_linux.cpp | 32 ++++++++++++++--- 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index ce4da02be..397f10056 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include #include #include #include @@ -88,7 +89,7 @@ void writeLog(const char *format, ...) { va_end(args); } -bool copyFile(const char *from, const char *to) { +bool copyFile(const char *from, const char *to, bool writeprotected) { FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); if (!ffrom) { if (fto) fclose(fto); @@ -134,7 +135,7 @@ bool copyFile(const char *from, const char *to) { } //update to the same uid/gid - if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { + if (!writeprotected && fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { fclose(ffrom); fclose(fto); return false; @@ -233,7 +234,7 @@ void delFolder() { rmdir(delFolder.c_str()); } -bool update() { +bool update(bool writeprotected) { writeLog("Update started.."); string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata"; @@ -346,7 +347,7 @@ bool update() { writeLog("Copying file '%s' to '%s'..", fname.c_str(), tofname.c_str()); int copyTries = 0, triesLimit = 30; do { - if (!copyFile(fname.c_str(), tofname.c_str())) { + if (!copyFile(fname.c_str(), tofname.c_str(), writeprotected)) { ++copyTries; usleep(100000); } else { @@ -381,6 +382,7 @@ int main(int argc, char *argv[]) { bool needupdate = true; bool autostart = false; bool debug = false; + bool writeprotected = false; bool tosettings = false; bool startintray = false; bool testmode = false; @@ -389,6 +391,8 @@ int main(int argc, char *argv[]) { char *key = 0; char *workdir = 0; + char *oldUsername = 0; + char *dbusAddress = 0; for (int i = 1; i < argc; ++i) { if (equal(argv[i], "-noupdate")) { needupdate = false; @@ -408,12 +412,17 @@ int main(int argc, char *argv[]) { customWorkingDir = true; } else if (equal(argv[i], "-key") && ++i < argc) { key = argv[i]; + } else if (equal(argv[i], "-writeprotected") && ++i < argc) { + writeprotected = true; + oldUsername = argv[i]; } else if (equal(argv[i], "-workpath") && ++i < argc) { workDir = workdir = argv[i]; } else if (equal(argv[i], "-exename") && ++i < argc) { exeName = argv[i]; } else if (equal(argv[i], "-exepath") && ++i < argc) { exePath = argv[i]; + } else if (equal(argv[i], "-dbus") && ++i < argc) { + dbusAddress = argv[i]; } } if (exeName.empty() || exeName.find('/') != string::npos) { @@ -427,6 +436,7 @@ int main(int argc, char *argv[]) { } if (needupdate) writeLog("Need to update!"); if (autostart) writeLog("From autostart!"); + if (writeprotected) writeLog("Write Protected folder!"); updaterName = CurrentExecutablePath(argc, argv); writeLog("Updater binary full path is: %s", updaterName.c_str()); @@ -477,7 +487,7 @@ int main(int argc, char *argv[]) { } else { writeLog("Passed workpath is '%s'", workDir.c_str()); } - update(); + update(writeprotected); } } else { writeLog("Error: bad exe name!"); @@ -494,6 +504,15 @@ int main(int argc, char *argv[]) { // Force null-terminated .data() call result. values.push_back(arg + char(0)); }; + if (writeprotected) { // run un-elevated + push("pkexec"); + push("--user"); + push(oldUsername); + push("env"); + push("DBUS_SESSION_BUS_ADDRESS=" + string(dbusAddress)); + push("systemd-run"); // restore environment + push("--user"); + } push(path); push("-noupdate"); if (autostart) push("-autostart"); @@ -523,10 +542,15 @@ int main(int argc, char *argv[]) { writeLog("fork() failed!"); return 1; case 0: - execv(path, args.data()); + execvp(args[0], args.data()); return 1; } + // pkexec needs an alive parent + if (writeprotected) { + waitpid(pid, nullptr, 0); + } + writeLog("Executed Telegram, closing log and quitting.."); closeLog(); diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index a07e29d25..f96d85f58 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -45,6 +45,10 @@ extern "C" { #include #endif // else of Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED +#ifdef Q_OS_UNIX +#include +#endif // Q_OS_UNIX + namespace Core { namespace { @@ -1553,9 +1557,25 @@ bool checkReadyUpdate() { return false; } #elif defined Q_OS_UNIX // Q_OS_MAC + // if the files in the directory are owned by user, while the directory is not, + // update will still fail since it's not possible to remove files + if (unlink(QFile::encodeName(curUpdater).constData())) { + if (errno == EACCES) { + cSetWriteProtected(true); + return true; + } else { + ClearAll(); + return false; + } + } if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { - ClearAll(); - return false; + if (errno == EACCES) { + cSetWriteProtected(true); + return true; + } else { + ClearAll(); + return false; + } } #endif // Q_OS_UNIX diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index fe219721e..6bb9bffd4 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include #include #include #include @@ -75,12 +76,17 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { return false; } - const auto binaryName = (action == UpdaterLaunch::JustRelaunch) - ? cExeName() - : QStringLiteral("Updater"); + const auto binaryPath = (action == UpdaterLaunch::JustRelaunch) + ? (cExeDir() + cExeName()) + : (cWriteProtected() + ? (cWorkingDir() + qsl("tupdates/temp/Updater")) + : (cExeDir() + qsl("Updater"))); auto argumentsList = Arguments(); - argumentsList.push(QFile::encodeName(cExeDir() + binaryName)); + if (action != UpdaterLaunch::JustRelaunch && cWriteProtected()) { + argumentsList.push("pkexec"); + } + argumentsList.push(QFile::encodeName(binaryPath)); if (cLaunchMode() == LaunchModeAutoStart) { argumentsList.push("-autostart"); @@ -118,6 +124,16 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { if (customWorkingDir()) { argumentsList.push("-workdir_custom"); } + if (cWriteProtected()) { + const auto currentUid = geteuid(); + const auto pw = getpwuid(currentUid); + if (pw) { + argumentsList.push("-writeprotected"); + argumentsList.push(pw->pw_name); + argumentsList.push("-dbus"); + argumentsList.push(qgetenv("DBUS_SESSION_BUS_ADDRESS")); + } + } } Logs::closeMain(); @@ -128,8 +144,14 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { pid_t pid = fork(); switch (pid) { case -1: return false; - case 0: execv(args[0], args); return false; + case 0: execvp(args[0], args); return false; } + + // pkexec needs an alive parent + if (cWriteProtected()) { + waitpid(pid, nullptr, 0); + } + return true; } From 99e70f7783935401fad367968288e0af8c0dc850 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 3 May 2021 11:04:07 +0400 Subject: [PATCH 023/477] Build Qt with libproxy --- Telegram/build/docker/centos_env/Dockerfile | 22 +++++++++++++++++++++ cmake | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 5179f6f07..bb23c5899 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -67,6 +67,23 @@ RUN DESTDIR="$LibrariesPath/xz-cache" cmake3 --install build WORKDIR .. RUN rm -rf xz +FROM patches AS libproxy +RUN git clone -b 0.4.17 --depth=1 $GIT/libproxy/libproxy.git + +WORKDIR libproxy +RUN git apply ../patches/libproxy.patch +RUN cmake3 -B build . \ + -DCMAKE_BUILD_TYPE=Release \ + -DWITH_DBUS=OFF \ + -DWITH_NM=OFF \ + -DWITH_NMold=OFF + +RUN cmake3 --build build -j$(nproc) +RUN DESTDIR="$LibrariesPath/libproxy-cache" cmake3 --install build + +WORKDIR .. +RUN rm -rf libproxy + FROM builder AS mozjpeg RUN git clone -b v4.0.1-rc2 --depth=1 $GIT/mozilla/mozjpeg.git @@ -508,6 +525,7 @@ RUN rm -rf glibmm FROM patches AS qt COPY --from=libffi ${LibrariesPath}/libffi-cache / +COPY --from=libproxy ${LibrariesPath}/libproxy-cache / COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=xcb ${LibrariesPath}/xcb-cache / COPY --from=xcb-wm ${LibrariesPath}/xcb-wm-cache / @@ -537,6 +555,7 @@ RUN echo './configure -prefix '$'\"''$QT_PREFIX'$'\"'' \ -opensource \ -confirm-license \ -xcb \ + -libproxy \ -qt-libpng \ -qt-harfbuzz \ -qt-pcre \ @@ -549,6 +568,7 @@ RUN echo './configure -prefix '$'\"''$QT_PREFIX'$'\"'' \ -openssl-linked \ -I '$'\"''$OPENSSL_PREFIX/include'$'\"'' \ OPENSSL_LIBS='$'\"''$OPENSSL_PREFIX/lib/libssl.a $OPENSSL_PREFIX/lib/libcrypto.a -lz -ldl -lpthread'$'\"'' \ + LIBPROXY_LIBS='$'\"''-lproxy -ldl'$'\"'' \ -nomake examples \ -nomake tests' >> ./run_configure.sh RUN cat ./run_configure.sh @@ -566,6 +586,7 @@ FROM builder AS kwayland COPY --from=extra-cmake-modules ${LibrariesPath}/extra-cmake-modules-cache / COPY --from=libffi ${LibrariesPath}/libffi-cache / +COPY --from=libproxy ${LibrariesPath}/libproxy-cache / COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=xcb ${LibrariesPath}/xcb-cache / COPY --from=xcb-wm ${LibrariesPath}/xcb-wm-cache / @@ -665,6 +686,7 @@ FROM builder COPY --from=libffi ${LibrariesPath}/libffi-cache / COPY --from=xz ${LibrariesPath}/xz-cache / +COPY --from=libproxy ${LibrariesPath}/libproxy-cache / COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=opus ${LibrariesPath}/opus-cache / COPY --from=xcb ${LibrariesPath}/xcb-cache / diff --git a/cmake b/cmake index 6d1b48811..1720a174c 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 6d1b48811e9f3c74072aa1cb9e0ea07d17985fe1 +Subproject commit 1720a174c4d96bc1512837adcae257c6637438e5 From 7444f17c4ecf0de6d27d88010320a6d8de3a5fdf Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 14:59:24 +0400 Subject: [PATCH 024/477] Use sendfile only on Linux. --- .../platform/linux/specific_linux.cpp | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index ea14312b9..b483c2d05 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -52,7 +52,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#ifdef Q_OS_LINUX #include +#endif // Q_OS_LINUX #include #include #include @@ -1013,6 +1015,14 @@ void psAutoStart(bool start, bool silent) { void psSendToMenu(bool send, bool silent) { } +void sendfileFallback(FILE *out, FILE *in) { + static const int BufSize = 65536; + char buf[BufSize]; + while (size_t size = fread(buf, 1, BufSize, in)) { + fwrite(buf, 1, size, out); + } +} + bool linuxMoveFile(const char *from, const char *to) { FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb"); if (!ffrom) { @@ -1032,26 +1042,19 @@ bool linuxMoveFile(const char *from, const char *to) { return false; } - ssize_t copied = -1; #ifdef Q_OS_LINUX - copied = sendfile( + ssize_t copied = sendfile( fileno(fto), fileno(ffrom), nullptr, fst.st_size); -#endif // Q_OS_LINUX - if (copied == -1) { DEBUG_LOG(("Update Error: " "Copy by sendfile '%1' to '%2' failed, error: %3, fallback now." ).arg(from ).arg(to ).arg(errno)); - static const int BufSize = 65536; - char buf[BufSize]; - while (size_t size = fread(buf, 1, BufSize, ffrom)) { - fwrite(buf, 1, size, fto); - } + sendfileFallback(fto, ffrom); } else { DEBUG_LOG(("Update Info: " "Copy by sendfile '%1' to '%2' done, size: %3, result: %4." @@ -1060,6 +1063,9 @@ bool linuxMoveFile(const char *from, const char *to) { ).arg(fst.st_size ).arg(copied)); } +#else // Q_OS_LINUX + sendfileFallback(fto, ffrom); +#endif // Q_OS_LINUX //update to the same uid/gid if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) { From ee00f1213136375d9177f7aa2491e34142d9e9fc Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 3 May 2021 14:21:17 +0400 Subject: [PATCH 025/477] Launch new version directly in write-protected mode --- Telegram/SourceFiles/_other/updater_linux.cpp | 43 ++++++------------- .../platform/linux/launcher_linux.cpp | 11 ++--- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/Telegram/SourceFiles/_other/updater_linux.cpp b/Telegram/SourceFiles/_other/updater_linux.cpp index 397f10056..c5cfe1269 100644 --- a/Telegram/SourceFiles/_other/updater_linux.cpp +++ b/Telegram/SourceFiles/_other/updater_linux.cpp @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include #include #include #include @@ -391,8 +390,6 @@ int main(int argc, char *argv[]) { char *key = 0; char *workdir = 0; - char *oldUsername = 0; - char *dbusAddress = 0; for (int i = 1; i < argc; ++i) { if (equal(argv[i], "-noupdate")) { needupdate = false; @@ -410,19 +407,16 @@ int main(int argc, char *argv[]) { tosettings = true; } else if (equal(argv[i], "-workdir_custom")) { customWorkingDir = true; + } else if (equal(argv[i], "-writeprotected")) { + writeprotected = true; } else if (equal(argv[i], "-key") && ++i < argc) { key = argv[i]; - } else if (equal(argv[i], "-writeprotected") && ++i < argc) { - writeprotected = true; - oldUsername = argv[i]; } else if (equal(argv[i], "-workpath") && ++i < argc) { workDir = workdir = argv[i]; } else if (equal(argv[i], "-exename") && ++i < argc) { exeName = argv[i]; } else if (equal(argv[i], "-exepath") && ++i < argc) { exePath = argv[i]; - } else if (equal(argv[i], "-dbus") && ++i < argc) { - dbusAddress = argv[i]; } } if (exeName.empty() || exeName.find('/') != string::npos) { @@ -504,15 +498,6 @@ int main(int argc, char *argv[]) { // Force null-terminated .data() call result. values.push_back(arg + char(0)); }; - if (writeprotected) { // run un-elevated - push("pkexec"); - push("--user"); - push(oldUsername); - push("env"); - push("DBUS_SESSION_BUS_ADDRESS=" + string(dbusAddress)); - push("systemd-run"); // restore environment - push("--user"); - } push(path); push("-noupdate"); if (autostart) push("-autostart"); @@ -536,19 +521,17 @@ int main(int argc, char *argv[]) { } args.push_back(nullptr); - pid_t pid = fork(); - switch (pid) { - case -1: - writeLog("fork() failed!"); - return 1; - case 0: - execvp(args[0], args.data()); - return 1; - } - - // pkexec needs an alive parent - if (writeprotected) { - waitpid(pid, nullptr, 0); + // let the parent launch instead + if (!writeprotected) { + pid_t pid = fork(); + switch (pid) { + case -1: + writeLog("fork() failed!"); + return 1; + case 0: + execv(args[0], args.data()); + return 1; + } } writeLog("Executed Telegram, closing log and quitting.."); diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index 6bb9bffd4..3f8489502 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -125,14 +125,7 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { argumentsList.push("-workdir_custom"); } if (cWriteProtected()) { - const auto currentUid = geteuid(); - const auto pw = getpwuid(currentUid); - if (pw) { - argumentsList.push("-writeprotected"); - argumentsList.push(pw->pw_name); - argumentsList.push("-dbus"); - argumentsList.push(qgetenv("DBUS_SESSION_BUS_ADDRESS")); - } + argumentsList.push("-writeprotected"); } } @@ -150,6 +143,8 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { // pkexec needs an alive parent if (cWriteProtected()) { waitpid(pid, nullptr, 0); + // launch new version in the same environment + return launchUpdater(UpdaterLaunch::JustRelaunch); } return true; From 68dc00be27e9697051225bc53dfe6587b85bbf5c Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 3 May 2021 14:22:58 +0400 Subject: [PATCH 026/477] Move weak_ptr include to .cpp in linux notifications --- .../platform/linux/notifications_manager_linux.cpp | 1 + .../SourceFiles/platform/linux/notifications_manager_linux.h | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 399b8d799..e5a8a27ff 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "main/main_session.h" #include "lang/lang_keys.h" +#include "base/weak_ptr.h" #include #include diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h index d4becaf7e..c3ff1c0a8 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h @@ -8,14 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "platform/platform_notifications_manager.h" -#include "base/weak_ptr.h" namespace Platform { namespace Notifications { -class Manager - : public Window::Notifications::NativeManager - , public base::has_weak_ptr { +class Manager : public Window::Notifications::NativeManager { public: Manager(not_null system); void clearNotification(NotificationId id); From 9a857659ce8c9bfcaf6277d569d1df6e14f25566 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 3 May 2021 15:48:31 +0400 Subject: [PATCH 027/477] Check action type before launching new version --- Telegram/SourceFiles/platform/linux/launcher_linux.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp index 3f8489502..f358fbaec 100644 --- a/Telegram/SourceFiles/platform/linux/launcher_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/launcher_linux.cpp @@ -83,7 +83,7 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { : (cExeDir() + qsl("Updater"))); auto argumentsList = Arguments(); - if (action != UpdaterLaunch::JustRelaunch && cWriteProtected()) { + if (action == UpdaterLaunch::PerformUpdate && cWriteProtected()) { argumentsList.push("pkexec"); } argumentsList.push(QFile::encodeName(binaryPath)); @@ -141,7 +141,7 @@ bool Launcher::launchUpdater(UpdaterLaunch action) { } // pkexec needs an alive parent - if (cWriteProtected()) { + if (action == UpdaterLaunch::PerformUpdate && cWriteProtected()) { waitpid(pid, nullptr, 0); // launch new version in the same environment return launchUpdater(UpdaterLaunch::JustRelaunch); From b1c4524612fcfbb95586f21b2c41be6c5c538db7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 17:34:33 +0400 Subject: [PATCH 028/477] Fix crash dump generation on Linux. --- Telegram/SourceFiles/core/crash_reports.cpp | 234 ++++++++---------- Telegram/SourceFiles/core/update_checker.cpp | 9 +- .../platform/linux/specific_linux.cpp | 3 - .../platform/linux/specific_linux.h | 5 +- .../SourceFiles/platform/mac/specific_mac.h | 2 - .../SourceFiles/platform/mac/specific_mac.mm | 14 +- .../SourceFiles/platform/platform_specific.h | 3 +- .../SourceFiles/platform/win/specific_win.cpp | 50 ++-- .../SourceFiles/platform/win/specific_win.h | 2 - 9 files changed, 145 insertions(+), 177 deletions(-) diff --git a/Telegram/SourceFiles/core/crash_reports.cpp b/Telegram/SourceFiles/core/crash_reports.cpp index 232794298..f763fcd45 100644 --- a/Telegram/SourceFiles/core/crash_reports.cpp +++ b/Telegram/SourceFiles/core/crash_reports.cpp @@ -131,20 +131,98 @@ void InstallQtMessageHandler() { }); } -Qt::HANDLE ReportingThreadId = nullptr; -bool ReportingHeaderWritten = false; -QMutex ReportingMutex; +std::atomic ReportingThreadId/* = nullptr*/; +bool ReportingHeaderWritten/* = false*/; +const char *BreakpadDumpPath/* = nullptr*/; +const wchar_t *BreakpadDumpPathW/* = nullptr*/; -const char *BreakpadDumpPath = nullptr; -const wchar_t *BreakpadDumpPathW = nullptr; +const int HandledSignals[] = { + SIGSEGV, + SIGABRT, + SIGFPE, + SIGILL, +#ifdef Q_OS_UNIX + SIGBUS, + SIGTRAP, +#endif // Q_OS_UNIX +}; #ifdef Q_OS_UNIX -struct sigaction SIG_def[32]; +struct sigaction OldSigActions[32]/* = { 0 }*/; + +void RestoreSignalHandlers() { + for (const auto signal : HandledSignals) { + sigaction(signal, &OldSigActions[signal], nullptr); + } +} + +void InvokeOldSignalHandler(int signum, siginfo_t *info, void *ucontext) { + if (signum < 0 || signum > 31) { + return; + } else if (OldSigActions[signum].sa_flags & SA_SIGINFO) { + if (OldSigActions[signum].sa_sigaction) { + OldSigActions[signum].sa_sigaction(signum, info, ucontext); + } + } else { + if (OldSigActions[signum].sa_handler) { + OldSigActions[signum].sa_handler(signum); + } + } +} + +void WriteReportHeader() { + if (ReportingHeaderWritten) { + return; + } + ReportingHeaderWritten = true; + const auto dec2hex = [](int value) -> char { + if (value >= 0 && value < 10) { + return '0' + value; + } else if (value >= 10 && value < 16) { + return 'a' + (value - 10); + } + return '#'; + }; + for (const auto &i : ProcessAnnotationRefs) { + QByteArray utf8 = i.second->toUtf8(); + std::string wrapped; + wrapped.reserve(4 * utf8.size()); + for (auto ch : utf8) { + auto uch = static_cast(ch); + wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F)); + } + ProcessAnnotations[i.first] = wrapped; + } + for (const auto &i : ProcessAnnotations) { + dump() << i.first.c_str() << ": " << i.second.c_str() << "\n"; + } + Platform::WriteCrashDumpDetails(); + dump() << "\n"; +} + +void WriteReportInfo(int signum, const char *name) { + WriteReportHeader(); + + const auto thread = ReportingThreadId.load(); + if (name) { + dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n"; + } else if (signum == -1) { + dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n"; + if (BreakpadDumpPath) { + dump() << "Minidump: " << BreakpadDumpPath << "\n"; + } else if (BreakpadDumpPathW) { + dump() << "Minidump: " << BreakpadDumpPathW << "\n"; + } + } else { + dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n"; + } + + dump() << "\nBacktrace omitted.\n"; + dump() << "\n"; +} void SignalHandler(int signum, siginfo_t *info, void *ucontext) { - if (signum > 0) { - sigaction(signum, &SIG_def[signum], 0); - } + RestoreSignalHandlers(); #else // Q_OS_UNIX void SignalHandler(int signum) { @@ -162,125 +240,17 @@ void SignalHandler(int signum) { #endif // !Q_OS_WIN } - Qt::HANDLE thread = QThread::currentThreadId(); - if (thread == ReportingThreadId) return; + auto expected = Qt::HANDLE(nullptr); + const auto thread = QThread::currentThreadId(); - QMutexLocker lock(&ReportingMutex); - ReportingThreadId = thread; - - if (!ReportingHeaderWritten) { - ReportingHeaderWritten = true; - auto dec2hex = [](int value) -> char { - if (value >= 0 && value < 10) { - return '0' + value; - } else if (value >= 10 && value < 16) { - return 'a' + (value - 10); - } - return '#'; - }; - - for (const auto &i : ProcessAnnotationRefs) { - QByteArray utf8 = i.second->toUtf8(); - std::string wrapped; - wrapped.reserve(4 * utf8.size()); - for (auto ch : utf8) { - auto uch = static_cast(ch); - wrapped.append("\\x", 2).append(1, dec2hex(uch >> 4)).append(1, dec2hex(uch & 0x0F)); - } - ProcessAnnotations[i.first] = wrapped; - } - - for (const auto &i : ProcessAnnotations) { - dump() << i.first.c_str() << ": " << i.second.c_str() << "\n"; - } - psWriteDump(); - dump() << "\n"; - } - if (name) { - dump() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n"; - } else if (signum == -1) { - dump() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n"; - if (BreakpadDumpPath) { - dump() << "Minidump: " << BreakpadDumpPath << "\n"; - } else if (BreakpadDumpPathW) { - dump() << "Minidump: " << BreakpadDumpPathW << "\n"; - } - } else { - dump() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n"; + if (ReportingThreadId.compare_exchange_strong(expected, thread)) { + WriteReportInfo(signum, name); + ReportingThreadId = nullptr; } - // see https://github.com/benbjohnson/bandicoot #ifdef Q_OS_UNIX - ucontext_t *uc = (ucontext_t*)ucontext; - - void *caller = 0; - if (uc) { -#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) - /* OSX < 10.6 */ -#if defined(__x86_64__) - caller = (void*)uc->uc_mcontext->__ss.__rip; -#elif defined(__i386__) - caller = (void*)uc->uc_mcontext->__ss.__eip; -#else - caller = (void*)uc->uc_mcontext->__ss.__srr0; -#endif -#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) - /* OSX >= 10.6 */ -#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) - caller = (void*)uc->uc_mcontext->__ss.__rip; -#else - caller = (void*)uc->uc_mcontext->__ss.__eip; -#endif -#elif defined(__linux__) - /* Linux */ -#if defined(__i386__) - caller = (void*)uc->uc_mcontext.gregs[14]; /* Linux 32 */ -#elif defined(__X86_64__) || defined(__x86_64__) - caller = (void*)uc->uc_mcontext.gregs[16]; /* Linux 64 */ -#elif defined(__ia64__) /* Linux IA64 */ - caller = (void*)uc->uc_mcontext.sc_ip; -#endif - -#endif - } - - void *addresses[132] = { 0 }; - size_t size = backtrace(addresses, 128); - - /* overwrite sigaction with caller's address */ - if (caller) { - for (int i = size; i > 1; --i) { - addresses[i + 3] = addresses[i]; - } - addresses[2] = (void*)0x1; - addresses[3] = caller; - addresses[4] = (void*)0x1; - } - -#ifdef Q_OS_MAC - dump() << "\nBase image addresses:\n"; - for (size_t i = 0; i < size; ++i) { - Dl_info info; - dump() << i << " "; - if (dladdr(addresses[i], &info)) { - dump() << uint64(info.dli_fbase) << " (" << info.dli_fname << ")\n"; - } else { - dump() << "_unknown_module_\n"; - } - } -#endif // Q_OS_MAC - - dump() << "\nBacktrace:\n"; - - backtrace_symbols_fd(addresses, size, ReportFileNo); - -#else // Q_OS_UNIX - dump() << "\nBacktrace omitted.\n"; -#endif // else for Q_OS_UNIX - - dump() << "\n"; - - ReportingThreadId = nullptr; + InvokeOldSignalHandler(signum, info, ucontext); +#endif // Q_OS_UNIX } bool SetSignalHandlers = true; @@ -475,17 +445,13 @@ Status Restart() { sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; - sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]); - sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]); - sigaction(SIGILL, &sigact, &SIG_def[SIGILL]); - sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]); - sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]); - sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]); + for (const auto signal : HandledSignals) { + sigaction(signal, &sigact, &OldSigActions[signal]); + } #else // !Q_OS_WIN - signal(SIGABRT, SignalHandler); - signal(SIGSEGV, SignalHandler); - signal(SIGILL, SignalHandler); - signal(SIGFPE, SignalHandler); + for (const auto signal : HandledSignals) { + signal(signal, SignalHandler); + } #endif // else for !Q_OS_WIN } diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index f96d85f58..5f42cfb75 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -1559,20 +1559,27 @@ bool checkReadyUpdate() { #elif defined Q_OS_UNIX // Q_OS_MAC // if the files in the directory are owned by user, while the directory is not, // update will still fail since it's not possible to remove files - if (unlink(QFile::encodeName(curUpdater).constData())) { + if (QFile::exists(curUpdater) + && unlink(QFile::encodeName(curUpdater).constData())) { if (errno == EACCES) { + DEBUG_LOG(("Update Info: " + "could not unlink current Updater, access denied.")); cSetWriteProtected(true); return true; } else { + DEBUG_LOG(("Update Error: could not unlink current Updater.")); ClearAll(); return false; } } if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) { if (errno == EACCES) { + DEBUG_LOG(("Update Info: " + "could not copy new Updater, access denied.")); cSetWriteProtected(true); return true; } else { + DEBUG_LOG(("Update Error: could not copy new Updater.")); ClearAll(); return false; } diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index b483c2d05..890a7c0c4 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -650,9 +650,6 @@ bool SkipTaskbarSupported() { } // namespace Platform -void psWriteDump() { -} - void psActivateProcess(uint64 pid) { // objc_activateProgram(); } diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.h b/Telegram/SourceFiles/platform/linux/specific_linux.h index c1ce882ba..7edf24c5c 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.h +++ b/Telegram/SourceFiles/platform/linux/specific_linux.h @@ -26,6 +26,9 @@ void InstallLauncher(bool force = false); inline void IgnoreApplicationActivationRightNow() { } +inline void WriteCrashDumpDetails() { +} + } // namespace Platform inline void psCheckLocalSocket(const QString &serverName) { @@ -35,8 +38,6 @@ inline void psCheckLocalSocket(const QString &serverName) { } } -void psWriteDump(); - void psActivateProcess(uint64 pid = 0); QString psAppDataPath(); void psAutoStart(bool start, bool silent = false); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.h b/Telegram/SourceFiles/platform/mac/specific_mac.h index 91b97a584..adc5d3cdd 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.h +++ b/Telegram/SourceFiles/platform/mac/specific_mac.h @@ -52,8 +52,6 @@ inline void psCheckLocalSocket(const QString &serverName) { } } -void psWriteDump(); - void psActivateProcess(uint64 pid = 0); QString psAppDataPath(); void psAutoStart(bool start, bool silent = false); diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index d6726a80a..57456f608 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -37,13 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include -void psWriteDump() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS - double v = objc_appkitVersion(); - CrashReports::dump() << "OS-Version: " << v; -#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS -} - void psActivateProcess(uint64 pid) { if (!pid) { const auto window = Core::App().activeWindow(); @@ -113,6 +106,13 @@ std::optional IsDarkMode() { : std::nullopt; } +void WriteCrashDumpDetails() { +#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS + double v = objc_appkitVersion(); + CrashReports::dump() << "OS-Version: " << v; +#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS +} + void RegisterCustomScheme(bool force) { OSStatus result = LSSetDefaultHandlerForURLScheme(CFSTR("tg"), (CFStringRef)[[NSBundle mainBundle] bundleIdentifier]); DEBUG_LOG(("App Info: set default handler for 'tg' scheme result: %1").arg(result)); diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index d0bfb84dc..f7112b214 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -39,7 +39,8 @@ void IgnoreApplicationActivationRightNow(); bool AutostartSupported(); bool TrayIconSupported(); bool SkipTaskbarSupported(); -QImage GetImageFromClipboard(); +[[nodiscard]] QImage GetImageFromClipboard(); +void WriteCrashDumpDetails(); [[nodiscard]] std::optional IsDarkMode(); [[nodiscard]] inline bool IsDarkModeSupported() { diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 0f323cb51..b0f9d5ac8 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -293,6 +293,31 @@ bool AutostartSupported() { return !IsWindowsStoreBuild(); } +void WriteCrashDumpDetails() { +#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS + PROCESS_MEMORY_COUNTERS data = { 0 }; + if (Dlls::GetProcessMemoryInfo + && Dlls::GetProcessMemoryInfo( + GetCurrentProcess(), + &data, + sizeof(data))) { + const auto mb = 1024 * 1024; + CrashReports::dump() + << "Memory-usage: " + << (data.PeakWorkingSetSize / mb) + << " MB (peak), " + << (data.WorkingSetSize / mb) + << " MB (current)\n"; + CrashReports::dump() + << "Pagefile-usage: " + << (data.PeakPagefileUsage / mb) + << " MB (peak), " + << (data.PagefileUsage / mb) + << " MB (current)\n"; + } +#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS +} + } // namespace Platform namespace { @@ -522,31 +547,6 @@ void psSendToMenu(bool send, bool silent) { _manageAppLnk(send, silent, CSIDL_SENDTO, L"-sendpath", L"Telegram send to link.\nYou can disable send to menu item in Telegram settings."); } -void psWriteDump() { -#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS - PROCESS_MEMORY_COUNTERS data = { 0 }; - if (Dlls::GetProcessMemoryInfo - && Dlls::GetProcessMemoryInfo( - GetCurrentProcess(), - &data, - sizeof(data))) { - const auto mb = 1024 * 1024; - CrashReports::dump() - << "Memory-usage: " - << (data.PeakWorkingSetSize / mb) - << " MB (peak), " - << (data.WorkingSetSize / mb) - << " MB (current)\n"; - CrashReports::dump() - << "Pagefile-usage: " - << (data.PeakPagefileUsage / mb) - << " MB (peak), " - << (data.PagefileUsage / mb) - << " MB (current)\n"; - } -#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS -} - bool psLaunchMaps(const Data::LocationPoint &point) { return QDesktopServices::openUrl(qsl("bingmaps:?lvl=16&collection=point.%1_%2_Point").arg(point.latAsString()).arg(point.lonAsString())); } diff --git a/Telegram/SourceFiles/platform/win/specific_win.h b/Telegram/SourceFiles/platform/win/specific_win.h index bd3d234e6..d35c3fcf3 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.h +++ b/Telegram/SourceFiles/platform/win/specific_win.h @@ -47,8 +47,6 @@ inline void finish() { inline void psCheckLocalSocket(const QString &) { } -void psWriteDump(); - void psActivateProcess(uint64 pid = 0); QString psAppDataPath(); QString psAppDataPathOld(); From 8332ba84508942ad80696f94c4231003f39ed5e5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 21:39:50 +0400 Subject: [PATCH 029/477] Fix build on Windows. --- Telegram/SourceFiles/core/crash_reports.cpp | 68 ++++++++++----------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/Telegram/SourceFiles/core/crash_reports.cpp b/Telegram/SourceFiles/core/crash_reports.cpp index f763fcd45..5a87113dd 100644 --- a/Telegram/SourceFiles/core/crash_reports.cpp +++ b/Telegram/SourceFiles/core/crash_reports.cpp @@ -136,40 +136,6 @@ bool ReportingHeaderWritten/* = false*/; const char *BreakpadDumpPath/* = nullptr*/; const wchar_t *BreakpadDumpPathW/* = nullptr*/; -const int HandledSignals[] = { - SIGSEGV, - SIGABRT, - SIGFPE, - SIGILL, -#ifdef Q_OS_UNIX - SIGBUS, - SIGTRAP, -#endif // Q_OS_UNIX -}; - -#ifdef Q_OS_UNIX -struct sigaction OldSigActions[32]/* = { 0 }*/; - -void RestoreSignalHandlers() { - for (const auto signal : HandledSignals) { - sigaction(signal, &OldSigActions[signal], nullptr); - } -} - -void InvokeOldSignalHandler(int signum, siginfo_t *info, void *ucontext) { - if (signum < 0 || signum > 31) { - return; - } else if (OldSigActions[signum].sa_flags & SA_SIGINFO) { - if (OldSigActions[signum].sa_sigaction) { - OldSigActions[signum].sa_sigaction(signum, info, ucontext); - } - } else { - if (OldSigActions[signum].sa_handler) { - OldSigActions[signum].sa_handler(signum); - } - } -} - void WriteReportHeader() { if (ReportingHeaderWritten) { return; @@ -221,6 +187,40 @@ void WriteReportInfo(int signum, const char *name) { dump() << "\n"; } +const int HandledSignals[] = { + SIGSEGV, + SIGABRT, + SIGFPE, + SIGILL, +#ifdef Q_OS_UNIX + SIGBUS, + SIGTRAP, +#endif // Q_OS_UNIX +}; + +#ifdef Q_OS_UNIX +struct sigaction OldSigActions[32]/* = { 0 }*/; + +void RestoreSignalHandlers() { + for (const auto signal : HandledSignals) { + sigaction(signal, &OldSigActions[signal], nullptr); + } +} + +void InvokeOldSignalHandler(int signum, siginfo_t *info, void *ucontext) { + if (signum < 0 || signum > 31) { + return; + } else if (OldSigActions[signum].sa_flags & SA_SIGINFO) { + if (OldSigActions[signum].sa_sigaction) { + OldSigActions[signum].sa_sigaction(signum, info, ucontext); + } + } else { + if (OldSigActions[signum].sa_handler) { + OldSigActions[signum].sa_handler(signum); + } + } +} + void SignalHandler(int signum, siginfo_t *info, void *ucontext) { RestoreSignalHandlers(); From 5ce1b0029180f0b1a5bee3eb7ae71136d73a47bb Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 23:05:22 +0400 Subject: [PATCH 030/477] Fix build on Windows #2. --- Telegram/SourceFiles/core/crash_reports.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/core/crash_reports.cpp b/Telegram/SourceFiles/core/crash_reports.cpp index 5a87113dd..39febb8d7 100644 --- a/Telegram/SourceFiles/core/crash_reports.cpp +++ b/Telegram/SourceFiles/core/crash_reports.cpp @@ -202,8 +202,8 @@ const int HandledSignals[] = { struct sigaction OldSigActions[32]/* = { 0 }*/; void RestoreSignalHandlers() { - for (const auto signal : HandledSignals) { - sigaction(signal, &OldSigActions[signal], nullptr); + for (const auto signum : HandledSignals) { + sigaction(signum, &OldSigActions[signal], nullptr); } } @@ -445,12 +445,12 @@ Status Restart() { sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; - for (const auto signal : HandledSignals) { - sigaction(signal, &sigact, &OldSigActions[signal]); + for (const auto signum : HandledSignals) { + sigaction(signum, &sigact, &OldSigActions[signal]); } #else // !Q_OS_WIN - for (const auto signal : HandledSignals) { - signal(signal, SignalHandler); + for (const auto signum : HandledSignals) { + signal(signum, SignalHandler); } #endif // else for !Q_OS_WIN } From db6b571f60de9997303d41cff4155ed4d8bbe703 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 21:42:59 +0400 Subject: [PATCH 031/477] Add voice chat admin event log filter in channels. --- .../SourceFiles/history/admin_log/history_admin_log_filter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp index e754e01f8..7f1c688b9 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp @@ -282,8 +282,8 @@ void FilterBox::Inner::createActionsCheckboxes(const FilterValue &filter) { addFlag(Flag::f_edit, tr::lng_admin_log_filter_messages_edited(tr::now)); if (isGroup) { addFlag(Flag::f_pinned, tr::lng_admin_log_filter_messages_pinned(tr::now)); - addFlag(Flag::f_group_call, tr::lng_admin_log_filter_voice_chats(tr::now)); } + addFlag(Flag::f_group_call, tr::lng_admin_log_filter_voice_chats(tr::now)); addFlag(Flag::f_invites, tr::lng_admin_log_filter_invite_links(tr::now)); addFlag(Flag::f_leave, tr::lng_admin_log_filter_members_removed(tr::now)); } From 691dcb8ae1eecec2ca957eab88059eb0f3a906b3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 May 2021 00:02:24 +0400 Subject: [PATCH 032/477] Fix build on macOS and Linux. --- Telegram/SourceFiles/core/crash_reports.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/core/crash_reports.cpp b/Telegram/SourceFiles/core/crash_reports.cpp index 39febb8d7..022c6a14c 100644 --- a/Telegram/SourceFiles/core/crash_reports.cpp +++ b/Telegram/SourceFiles/core/crash_reports.cpp @@ -16,8 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS - -// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/ #ifdef Q_OS_WIN #pragma warning(push) @@ -25,11 +23,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "client/windows/handler/exception_handler.h" #pragma warning(pop) -#elif defined Q_OS_MAC // Q_OS_WIN +#elif defined Q_OS_UNIX // Q_OS_WIN #include -#include #include + +#ifdef Q_OS_MAC + #include #include @@ -39,16 +39,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "client/crashpad_client.h" #endif // else for MAC_USE_BREAKPAD -#elif defined Q_OS_UNIX // Q_OS_MAC - -#include -#include -#include +#else // Q_OS_MAC #include "client/linux/handler/exception_handler.h" -#endif // Q_OS_UNIX +#endif // Q_OS_MAC +#endif // Q_OS_WIN #endif // !DESKTOP_APP_DISABLE_CRASH_REPORTS namespace CrashReports { @@ -203,7 +200,7 @@ struct sigaction OldSigActions[32]/* = { 0 }*/; void RestoreSignalHandlers() { for (const auto signum : HandledSignals) { - sigaction(signum, &OldSigActions[signal], nullptr); + sigaction(signum, &OldSigActions[signum], nullptr); } } @@ -446,7 +443,7 @@ Status Restart() { sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; for (const auto signum : HandledSignals) { - sigaction(signum, &sigact, &OldSigActions[signal]); + sigaction(signum, &sigact, &OldSigActions[signum]); } #else // !Q_OS_WIN for (const auto signum : HandledSignals) { From f1a9884011ca3684afd36469ba0c439b17fc0927 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 May 2021 00:27:59 +0400 Subject: [PATCH 033/477] Beta version 2.7.5. - Add "Voice chats" filter in "Recent actions" for channels. - Write local drafts to disk on a background thread. - Support autoupdate for Telegram in write-protected folders on Linux. - Fix crash in native notifications on Linux. - Fix crash in file dialog on Linux. --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 ++++---- Telegram/Resources/winrc/Updater.rc | 8 ++++---- Telegram/SourceFiles/core/changelogs.cpp | 12 ++++++++++++ Telegram/SourceFiles/core/version.h | 6 +++--- Telegram/build/version | 10 +++++----- changelog.txt | 8 ++++++++ 7 files changed, 37 insertions(+), 17 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 877faddbe..b8f86fcf9 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="2.7.5.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index e1d53f467..36f6f9a8e 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 2,7,4,0 - PRODUCTVERSION 2,7,4,0 + FILEVERSION 2,7,5,0 + PRODUCTVERSION 2,7,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -62,10 +62,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop" - VALUE "FileVersion", "2.7.4.0" + VALUE "FileVersion", "2.7.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2021" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.7.4.0" + VALUE "ProductVersion", "2.7.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 911963678..7025dfa2e 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 2,7,4,0 - PRODUCTVERSION 2,7,4,0 + FILEVERSION 2,7,5,0 + PRODUCTVERSION 2,7,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -53,10 +53,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram FZ-LLC" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "2.7.4.0" + VALUE "FileVersion", "2.7.5.0" VALUE "LegalCopyright", "Copyright (C) 2014-2021" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "2.7.4.0" + VALUE "ProductVersion", "2.7.5.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/core/changelogs.cpp b/Telegram/SourceFiles/core/changelogs.cpp index 5a8daca60..eb465606c 100644 --- a/Telegram/SourceFiles/core/changelogs.cpp +++ b/Telegram/SourceFiles/core/changelogs.cpp @@ -147,6 +147,18 @@ std::map BetaLogs() { "- MPRIS support on Linux.\n" }, + { + 2007005, + "- Add \"Voice chats\" filter in \"Recent actions\" for channels.\n" + + "- Write local drafts to disk on a background thread.\n" + + "- Support autoupdate for Telegram in write-protected folders on Linux.\n" + + "- Fix crash in native notifications on Linux.\n" + + "- Fix crash in file dialog on Linux.\n" + }, }; }; diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 612a6c548..2bfff42df 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D1ED}"_cs; constexpr auto AppNameOld = "Telegram Win (Unofficial)"_cs; constexpr auto AppName = "Telegram Desktop"_cs; constexpr auto AppFile = "Telegram"_cs; -constexpr auto AppVersion = 2007004; -constexpr auto AppVersionStr = "2.7.4"; -constexpr auto AppBetaVersion = false; +constexpr auto AppVersion = 2007005; +constexpr auto AppVersionStr = "2.7.5"; +constexpr auto AppBetaVersion = true; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; diff --git a/Telegram/build/version b/Telegram/build/version index 40699aa9c..f020eb098 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 2007004 +AppVersion 2007005 AppVersionStrMajor 2.7 -AppVersionStrSmall 2.7.4 -AppVersionStr 2.7.4 -BetaChannel 0 +AppVersionStrSmall 2.7.5 +AppVersionStr 2.7.5 +BetaChannel 1 AlphaVersion 0 -AppVersionOriginal 2.7.4 +AppVersionOriginal 2.7.5.beta diff --git a/changelog.txt b/changelog.txt index 7ac8ce48c..46cb3141f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,11 @@ +2.7.5 beta (04.05.21) + +- Add "Voice chats" filter in "Recent actions" for channels. +- Write local drafts to disk on a background thread. +- Support autoupdate for Telegram in write-protected folders on Linux. +- Fix crash in native notifications on Linux. +- Fix crash in file dialog on Linux. + 2.7.4 (28.04.21) - Fix crash in viewing an invoice after a payment is made. From 95b4435396c560e9bcfba8e56e167bb60913015b Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 6 May 2021 22:12:03 +0400 Subject: [PATCH 034/477] Resubscribe to signal when running with old xdg-desktop-portal --- .../platform/linux/linux_xdp_file_dialog.cpp | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp index dc65476e8..c9476a0c5 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp @@ -408,35 +408,37 @@ void XDPFileDialog::openPortal() { + uniqueName + '/' + handleToken; + + const auto responseCallback = crl::guard(this, [=]( + const Glib::RefPtr &connection, + const Glib::ustring &sender_name, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &signal_name, + const Glib::VariantContainerBase ¶meters) { + try { + auto parametersCopy = parameters; + + const auto response = base::Platform::GlibVariantCast( + parametersCopy.get_child(0)); + + const auto results = base::Platform::GlibVariantCast< + std::map< + Glib::ustring, + Glib::VariantBase + >>(parametersCopy.get_child(1)); + + gotResponse(response, results); + } catch (const std::exception &e) { + LOG(("XDP File Dialog Error: %1").arg( + QString::fromStdString(e.what()))); + + _reject.fire({}); + } + }); _requestSignalId = _dbusConnection->signal_subscribe( - crl::guard(this, [=]( - const Glib::RefPtr &connection, - const Glib::ustring &sender_name, - const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters) { - try { - auto parametersCopy = parameters; - - const auto response = base::Platform::GlibVariantCast( - parametersCopy.get_child(0)); - - const auto results = base::Platform::GlibVariantCast< - std::map< - Glib::ustring, - Glib::VariantBase - >>(parametersCopy.get_child(1)); - - gotResponse(response, results); - } catch (const std::exception &e) { - LOG(("XDP File Dialog Error: %1").arg( - QString::fromStdString(e.what()))); - - _reject.fire({}); - } - }), + responseCallback, {}, "org.freedesktop.portal.Request", "Response", @@ -461,10 +463,15 @@ void XDPFileDialog::openPortal() { Glib::ustring>(reply.get_child(0)); if (handle != requestPath) { - crl::on_main([=] { - _failedToOpen = true; - _reject.fire({}); - }); + _dbusConnection->signal_unsubscribe( + _requestSignalId); + + _requestSignalId = _dbusConnection->signal_subscribe( + responseCallback, + {}, + "org.freedesktop.portal.Request", + "Response", + handle); } } catch (const Glib::Error &e) { static const auto NotSupportedErrors = { From dba3c39726c1b642c51d811b55be16cc7ef68850 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 May 2021 13:53:54 +0400 Subject: [PATCH 035/477] Skip 'enhancement' issues by stale bot. --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index afc525a07..40a568cf2 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -3,7 +3,7 @@ daysUntilStale: 180 # Number of days of inactivity before a stale issue is closed daysUntilClose: 30 # Issues with these labels will never be considered stale -exemptLabels: [] +exemptLabels: [ "enhancement" ] # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable From 0cb32181c57b55ba474e9e956e3e39b12ab8f7a1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 May 2021 16:28:28 +0400 Subject: [PATCH 036/477] Handle small SLOWMODE_WAIT as FLOOD_WAIT. --- Telegram/SourceFiles/mtproto/mtp_instance.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp index 344e64ce7..e2cca13f5 100644 --- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp +++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp @@ -1266,12 +1266,12 @@ bool Instance::Private::onErrorDefault( int breakpoint = 0; } auto badGuestDc = (code == 400) && (type == qsl("FILE_ID_INVALID")); - QRegularExpressionMatch m; - if ((m = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$").match(type)).hasMatch()) { + QRegularExpressionMatch m1, m2; + if ((m1 = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$").match(type)).hasMatch()) { if (!requestId) return false; auto dcWithShift = ShiftedDcId(0); - auto newdcWithShift = ShiftedDcId(m.captured(2).toInt()); + auto newdcWithShift = ShiftedDcId(m1.captured(2).toInt()); if (const auto shiftedDcId = queryRequestByDc(requestId)) { dcWithShift = *shiftedDcId; } else { @@ -1329,7 +1329,11 @@ bool Instance::Private::onErrorDefault( (dcWithShift < 0) ? -newdcWithShift : newdcWithShift); session->sendPrepared(request); return true; - } else if (code < 0 || code >= 500 || (m = QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(type)).hasMatch()) { + } else if (code < 0 + || code >= 500 + || (m1 = QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(type)).hasMatch() + || ((m2 = QRegularExpression("^SLOWMODE_WAIT_(\\d+)$").match(type)).hasMatch() + && m2.captured(1).toInt() < 3)) { if (!requestId) return false; int32 secs = 1; @@ -1340,9 +1344,11 @@ bool Instance::Private::onErrorDefault( } else { _requestsDelays.emplace(requestId, secs); } - } else { - secs = m.captured(1).toInt(); + } else if (m1.hasMatch()) { + secs = m1.captured(1).toInt(); // if (secs >= 60) return false; + } else if (m2.hasMatch()) { + secs = m2.captured(1).toInt(); } auto sendAt = crl::now() + secs * 1000 + 10; auto it = _delayedRequests.begin(), e = _delayedRequests.end(); From d79fab8b3c66c4e8a1c98f27dbd2868939850ba4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 10 May 2021 16:45:21 +0400 Subject: [PATCH 037/477] Build ffmpeg with 'CONFIG_SAFE_BITSTREAM_READER' on macOS. --- docs/building-xcode.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building-xcode.md b/docs/building-xcode.md index a684fc555..f4ac055f9 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -125,8 +125,8 @@ Go to ***BuildPath*** and run cp ../patches/macos_yasm_wrap.sh ./ ./configure --prefix=/usr/local/macos \ - --extra-cflags="$MIN_VER $UNGUARDED" \ - --extra-cxxflags="$MIN_VER $UNGUARDED" \ + --extra-cflags="$MIN_VER $UNGUARDED -DCONFIG_SAFE_BITSTREAM_READER=1" \ + --extra-cxxflags="$MIN_VER $UNGUARDED -DCONFIG_SAFE_BITSTREAM_READER=1" \ --extra-ldflags="$MIN_VER" \ --x86asmexe=`pwd`/macos_yasm_wrap.sh \ --enable-protocol=file \ From 7de8d6f9ac56e1148369e9f4716244f452e23d49 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Tue, 11 May 2021 11:25:19 +0400 Subject: [PATCH 038/477] Fix resetting of available geometry fix in some cases --- Telegram/SourceFiles/window/main_window.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 0fc89fccb..cf23abd76 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -527,10 +527,10 @@ void MainWindow::initSize() { if (rightPoint > w) { const auto distance = rightPoint - w; const auto newXPos = position.x - distance; - if (newXPos >= 0) { + if (newXPos >= x) { position.x = newXPos; } else { - position.x = 0; + position.x = x; const auto newRightPoint = position.x + position.w; const auto newDistance = newRightPoint - w; position.w -= newDistance; @@ -540,10 +540,10 @@ void MainWindow::initSize() { if (bottomPoint > h) { const auto distance = bottomPoint - h; const auto newYPos = position.y - distance; - if (newYPos >= 0) { + if (newYPos >= y) { position.y = newYPos; } else { - position.y = 0; + position.y = y; const auto newBottomPoint = position.y + position.h; const auto newDistance = newBottomPoint - h; position.h -= newDistance; From 680a9a7ca7d9dde6c79a6340e3ca003e6ccfd8a5 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Mon, 10 May 2021 12:53:34 +0400 Subject: [PATCH 039/477] Implement parent setting for portal and gtk dialogs on Wayland via xdg-foreign-v2 --- .../platform/linux/linux_gdk_helper.cpp | 142 +++++++++++------- .../platform/linux/linux_gdk_helper.h | 4 +- .../platform/linux/linux_gtk_file_dialog.cpp | 5 +- .../linux/linux_gtk_open_with_dialog.cpp | 7 +- .../linux/linux_wayland_integration.cpp | 58 ++++++- .../linux/linux_wayland_integration.h | 1 + .../linux/linux_wayland_integration_dummy.cpp | 4 + .../platform/linux/linux_xdp_file_dialog.cpp | 18 ++- .../linux/linux_xdp_open_with_dialog.cpp | 29 ++-- .../platform/linux/specific_linux.cpp | 26 ++-- 10 files changed, 204 insertions(+), 90 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp index ab65c36cc..568c63406 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.cpp @@ -7,8 +7,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/linux_gdk_helper.h" +#include "base/platform/linux/base_linux_gtk_integration.h" #include "base/platform/linux/base_linux_gtk_integration_p.h" #include "platform/linux/linux_gtk_integration_p.h" +#include "platform/linux/linux_wayland_integration.h" + +#include #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION extern "C" { @@ -16,19 +20,21 @@ extern "C" { } // extern "C" #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION +// CentOS 7 seem to be too old for needed definitions, +// so don't include until we link to gtk directly. +#if !defined DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION && defined LINK_TO_GTK +extern "C" { +#include +} // extern "C" +#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION && LINK_TO_GTK + namespace Platform { namespace internal { +namespace { +using base::Platform::GtkIntegration; using namespace Platform::Gtk; -enum class GtkLoaded { - GtkNone, - Gtk2, - Gtk3, -}; - -GtkLoaded gdk_helper_loaded = GtkLoaded::GtkNone; - #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION // To be able to compile with gtk-3.0 headers as well #define GdkDrawable GdkWindow @@ -60,62 +66,92 @@ using f_gdk_x11_window_get_xid = Window(*)(GdkWindow *window); f_gdk_x11_window_get_xid gdk_x11_window_get_xid = nullptr; #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION -bool GdkHelperLoadGtk2(QLibrary &lib) { -#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION -#ifdef LINK_TO_GTK - return false; -#else // LINK_TO_GTK - if (!LOAD_GTK_SYMBOL(lib, gdk_x11_drawable_get_xdisplay)) return false; - if (!LOAD_GTK_SYMBOL(lib, gdk_x11_drawable_get_xid)) return false; - return true; -#endif // !LINK_TO_GTK -#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION - return false; -#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION +#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION +using f_gdk_wayland_window_get_type = GType (*)(void); +f_gdk_wayland_window_get_type gdk_wayland_window_get_type = nullptr; + +template +inline bool gdk_is_wayland_window_check(Object *obj) { + return g_type_cit_helper(obj, gdk_wayland_window_get_type()); } -bool GdkHelperLoadGtk3(QLibrary &lib) { +using f_gdk_wayland_window_set_transient_for_exported = gboolean(*)(GdkWindow *window, char *parent_handle_str); +f_gdk_wayland_window_set_transient_for_exported gdk_wayland_window_set_transient_for_exported = nullptr; +#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION + +void GdkHelperLoadGtk2(QLibrary &lib) { #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION - if (!LOAD_GTK_SYMBOL(lib, gdk_x11_window_get_type)) return false; - if (!LOAD_GTK_SYMBOL(lib, gdk_window_get_display)) return false; - if (!LOAD_GTK_SYMBOL(lib, gdk_x11_display_get_xdisplay)) return false; - if (!LOAD_GTK_SYMBOL(lib, gdk_x11_window_get_xid)) return false; - return true; -#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION - return false; -#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION + LOAD_GTK_SYMBOL(lib, gdk_x11_drawable_get_xdisplay); + LOAD_GTK_SYMBOL(lib, gdk_x11_drawable_get_xid); +#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION } +void GdkHelperLoadGtk3(QLibrary &lib) { +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION + LOAD_GTK_SYMBOL(lib, gdk_x11_window_get_type); + LOAD_GTK_SYMBOL(lib, gdk_window_get_display); + LOAD_GTK_SYMBOL(lib, gdk_x11_display_get_xdisplay); + LOAD_GTK_SYMBOL(lib, gdk_x11_window_get_xid); +#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION +#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION + LOAD_GTK_SYMBOL(lib, gdk_wayland_window_get_type); + LOAD_GTK_SYMBOL(lib, gdk_wayland_window_set_transient_for_exported); +#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION +} + +} // namespace + void GdkHelperLoad(QLibrary &lib) { - gdk_helper_loaded = GtkLoaded::GtkNone; - if (GdkHelperLoadGtk3(lib)) { - gdk_helper_loaded = GtkLoaded::Gtk3; - } else if (GdkHelperLoadGtk2(lib)) { - gdk_helper_loaded = GtkLoaded::Gtk2; + if (const auto integration = GtkIntegration::Instance()) { + if (integration->checkVersion(3, 0, 0)) { + GdkHelperLoadGtk3(lib); + } else { + GdkHelperLoadGtk2(lib); + } } } -bool GdkHelperLoaded() { -#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION - return gdk_helper_loaded != GtkLoaded::GtkNone; -#else // !DESKTOP_APP_DISABLE_X11_INTEGRATION - return true; -#endif // DESKTOP_APP_DISABLE_X11_INTEGRATION -} - -void XSetTransientForHint(GdkWindow *window, quintptr winId) { -#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION - if (gdk_helper_loaded == GtkLoaded::Gtk2) { - ::XSetTransientForHint(gdk_x11_drawable_get_xdisplay(window), - gdk_x11_drawable_get_xid(window), - winId); - } else if (gdk_helper_loaded == GtkLoaded::Gtk3) { - if (gdk_is_x11_window_check(window)) { - ::XSetTransientForHint(gdk_x11_display_get_xdisplay(gdk_window_get_display(window)), - gdk_x11_window_get_xid(window), - winId); +void GdkSetTransientFor(GdkWindow *window, QWindow *parent) { +#ifndef DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION + if (gdk_wayland_window_get_type != nullptr + && gdk_wayland_window_set_transient_for_exported != nullptr + && gdk_is_wayland_window_check(window)) { + if (const auto integration = WaylandIntegration::Instance()) { + if (const auto handle = integration->nativeHandle(parent) + ; !handle.isEmpty()) { + auto handleUtf8 = handle.toUtf8(); + gdk_wayland_window_set_transient_for_exported( + window, + handleUtf8.data()); + return; + } } } +#endif // !DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION + +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION + if (gdk_x11_window_get_type != nullptr + && gdk_x11_display_get_xdisplay != nullptr + && gdk_x11_window_get_xid != nullptr + && gdk_window_get_display != nullptr + && gdk_is_x11_window_check(window)) { + XSetTransientForHint( + gdk_x11_display_get_xdisplay(gdk_window_get_display(window)), + gdk_x11_window_get_xid(window), + parent->winId()); + return; + } +#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION + +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION + if (gdk_x11_drawable_get_xdisplay != nullptr + && gdk_x11_drawable_get_xid != nullptr) { + XSetTransientForHint( + gdk_x11_drawable_get_xdisplay(window), + gdk_x11_drawable_get_xid(window), + parent->winId()); + return; + } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION } diff --git a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h index 6ca953689..df06803c9 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h +++ b/Telegram/SourceFiles/platform/linux/linux_gdk_helper.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once class QLibrary; +class QWindow; extern "C" { #include @@ -18,8 +19,7 @@ namespace Platform { namespace internal { void GdkHelperLoad(QLibrary &lib); -bool GdkHelperLoaded(); -void XSetTransientForHint(GdkWindow *window, quintptr winId); +void GdkSetTransientFor(GdkWindow *window, QWindow *parent); } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp index 9befbf897..1c0039365 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp @@ -70,8 +70,7 @@ QStringList cleanFilterList(const QString &filter) { } bool Supported() { - return internal::GdkHelperLoaded() - && (gtk_widget_hide_on_delete != nullptr) + return (gtk_widget_hide_on_delete != nullptr) && (gtk_clipboard_store != nullptr) && (gtk_clipboard_get != nullptr) && (gtk_widget_destroy != nullptr) @@ -273,7 +272,7 @@ void QGtkDialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindo gtk_widget_realize(gtkWidget); // creates X window if (parent) { - internal::XSetTransientForHint(gtk_widget_get_window(gtkWidget), parent->winId()); + internal::GdkSetTransientFor(gtk_widget_get_window(gtkWidget), parent); } if (modality != Qt::NonModal) { diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.cpp index 1c8710bae..4994ebe7d 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_open_with_dialog.cpp @@ -22,8 +22,7 @@ namespace { using namespace Platform::Gtk; bool Supported() { - return Platform::internal::GdkHelperLoaded() - && (gtk_app_chooser_dialog_new != nullptr) + return (gtk_app_chooser_dialog_new != nullptr) && (gtk_app_chooser_get_app_info != nullptr) && (gtk_app_chooser_get_type != nullptr) && (gtk_widget_get_window != nullptr) @@ -70,9 +69,9 @@ bool GtkOpenWithDialog::exec() { gtk_widget_realize(_gtkWidget); if (const auto activeWindow = Core::App().activeWindow()) { - Platform::internal::XSetTransientForHint( + Platform::internal::GdkSetTransientFor( gtk_widget_get_window(_gtkWidget), - activeWindow->widget().get()->windowHandle()->winId()); + activeWindow->widget()->windowHandle()); } QGuiApplicationPrivate::showModalWindow(this); diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp index a3ff42761..6586988d8 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -9,8 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" +#include #include #include +#include +#include using namespace KWayland::Client; @@ -25,6 +28,10 @@ public: return _registry; } + [[nodiscard]] XdgExporter *xdgExporter() { + return _xdgExporter.get(); + } + [[nodiscard]] QEventLoop &interfacesLoop() { return _interfacesLoop; } @@ -35,12 +42,25 @@ public: private: ConnectionThread _connection; + ConnectionThread *_applicationConnection = nullptr; Registry _registry; + Registry _applicationRegistry; + std::unique_ptr _xdgExporter; QEventLoop _interfacesLoop; bool _interfacesAnnounced = false; }; -WaylandIntegration::Private::Private() { +WaylandIntegration::Private::Private() +: _applicationConnection(ConnectionThread::fromApplication(this)) { + _applicationRegistry.create(_applicationConnection); + _applicationRegistry.setup(); + + connect( + _applicationConnection, + &ConnectionThread::connectionDied, + &_applicationRegistry, + &Registry::destroy); + connect(&_connection, &ConnectionThread::connected, [=] { LOG(("Successfully connected to Wayland server at socket: %1") .arg(_connection.socketName())); @@ -62,6 +82,22 @@ WaylandIntegration::Private::Private() { } }); + connect( + &_applicationRegistry, + &Registry::exporterUnstableV2Announced, + [=](uint name, uint version) { + _xdgExporter = { + _applicationRegistry.createXdgExporter(name, version), + std::default_delete(), + }; + + connect( + QCoreApplication::instance(), + &QCoreApplication::aboutToQuit, + this, + [=] { _xdgExporter = nullptr; }); + }); + _connection.initConnection(); } @@ -89,5 +125,25 @@ bool WaylandIntegration::supportsXdgDecoration() { Registry::Interface::XdgDecorationUnstableV1); } +QString WaylandIntegration::nativeHandle(QWindow *window) { + if (const auto exporter = _private->xdgExporter()) { + if (const auto surface = Surface::fromWindow(window)) { + if (const auto exported = exporter->exportTopLevel( + surface, + _private->xdgExporter())) { + QEventLoop loop; + QObject::connect( + exported, + &XdgExported::done, + &loop, + &QEventLoop::quit); + loop.exec(); + return exported->handle(); + } + } + } + return {}; +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h index b2f716184..a41d34209 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h @@ -15,6 +15,7 @@ public: static WaylandIntegration *Instance(); void waitForInterfaceAnnounce(); bool supportsXdgDecoration(); + QString nativeHandle(QWindow *window); private: WaylandIntegration(); diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp index 166e8b575..2b4068553 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp @@ -33,5 +33,9 @@ bool WaylandIntegration::supportsXdgDecoration() { return false; } +QString WaylandIntegration::nativeHandle(QWindow *window) { + return {}; +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp index c9476a0c5..543908f24 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_file_utilities.h" #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_glibmm_helper.h" +#include "platform/linux/linux_wayland_integration.h" #include "storage/localstorage.h" #include "base/openssl_help.h" #include "base/qt_adapters.h" @@ -20,6 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +using Platform::internal::WaylandIntegration; + namespace Platform { namespace FileDialog { namespace XDP { @@ -207,7 +210,7 @@ private: uint _requestSignalId = 0; // Options - WId _winId = 0; + QWindow *_parent = nullptr; QFileDialog::Options _options; QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen; QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile; @@ -266,8 +269,13 @@ XDPFileDialog::~XDPFileDialog() { void XDPFileDialog::openPortal() { std::stringstream parentWindowId; - if (IsX11()) { - parentWindowId << "x11:" << std::hex << _winId; + if (const auto integration = WaylandIntegration::Instance()) { + if (const auto handle = integration->nativeHandle(_parent) + ; !handle.isEmpty()) { + parentWindowId << "wayland:" << handle.toStdString(); + } + } else if (IsX11() && _parent) { + parentWindowId << "x11:" << std::hex << _parent->winId(); } std::map options; @@ -408,7 +416,7 @@ void XDPFileDialog::openPortal() { + uniqueName + '/' + handleToken; - + const auto responseCallback = crl::guard(this, [=]( const Glib::RefPtr &connection, const Glib::ustring &sender_name, @@ -629,7 +637,7 @@ void XDPFileDialog::showHelper( Qt::WindowModality windowModality, QWindow *parent) { _modal = windowModality != Qt::NonModal; - _winId = parent ? parent->winId() : 0; + _parent = parent; openPortal(); } diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp index 64d3db95d..810bfeb21 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_open_with_dialog.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" #include "base/platform/linux/base_linux_glibmm_helper.h" +#include "platform/linux/linux_wayland_integration.h" #include "core/application.h" #include "window/window_controller.h" #include "base/openssl_help.h" @@ -21,6 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +using Platform::internal::WaylandIntegration; + namespace Platform { namespace File { namespace internal { @@ -80,18 +83,22 @@ bool XDPOpenWithDialog::exec() { const auto parentWindowId = [&]() -> Glib::ustring { std::stringstream result; - if (const auto activeWindow = Core::App().activeWindow()) { - if (IsX11()) { - result - << "x11:" - << std::hex - << activeWindow - ->widget() - .get() - ->windowHandle() - ->winId(); - } + + const auto activeWindow = Core::App().activeWindow(); + if (!activeWindow) { + return result.str(); } + + const auto window = activeWindow->widget()->windowHandle(); + if (const auto integration = WaylandIntegration::Instance()) { + if (const auto handle = integration->nativeHandle(window) + ; !handle.isEmpty()) { + result << "wayland:" << handle.toStdString(); + } + } else if (IsX11()) { + result << "x11:" << std::hex << window->winId(); + } + return result.str(); }(); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 890a7c0c4..971bf3a50 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -102,18 +102,22 @@ PortalAutostart::PortalAutostart(bool start, bool silent) { const auto parentWindowId = [&]() -> Glib::ustring { std::stringstream result; - if (const auto activeWindow = Core::App().activeWindow()) { - if (IsX11()) { - result - << "x11:" - << std::hex - << activeWindow - ->widget() - .get() - ->windowHandle() - ->winId(); - } + + const auto activeWindow = Core::App().activeWindow(); + if (!activeWindow) { + return result.str(); } + + const auto window = activeWindow->widget()->windowHandle(); + if (const auto integration = WaylandIntegration::Instance()) { + if (const auto handle = integration->nativeHandle(window) + ; !handle.isEmpty()) { + result << "wayland:" << handle.toStdString(); + } + } else if (IsX11()) { + result << "x11:" << std::hex << window->winId(); + } + return result.str(); }(); From ee156fc6a8ec26dc9168faa96790eb565a3ad4bf Mon Sep 17 00:00:00 2001 From: c0re100 Date: Mon, 19 Apr 2021 22:50:38 +0800 Subject: [PATCH 040/477] Add "Archive Sticker" into Sticker Box 3dots menu --- Telegram/Resources/langs/lang.strings | 2 + .../SourceFiles/boxes/sticker_set_box.cpp | 50 +++++++++++++++++++ Telegram/SourceFiles/boxes/sticker_set_box.h | 1 + 3 files changed, 53 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 234782058..d8d262f69 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1401,6 +1401,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_stickers_search_sets" = "Search sticker sets"; "lng_stickers_nothing_found" = "No stickers found"; "lng_stickers_remove_pack_confirm" = "Remove"; +"lng_stickers_archive_pack" = "Archive Stickers"; +"lng_stickers_has_been_archived" = "Sticker pack has been archived."; "lng_in_dlg_photo" = "Photo"; "lng_in_dlg_album" = "Album"; diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 5fbec8d49..343576326 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -198,6 +198,38 @@ void StickerSetBox::copyStickersLink() { QGuiApplication::clipboard()->setText(url); } +void StickerSetBox::archiveStickers() { + const auto weak = base::make_weak(_controller.get()); + const auto setId = _set.c_inputStickerSetID().vid().v; + _controller->session().api().request(MTPmessages_InstallStickerSet( + _set, + MTP_boolTrue() + )).done([=](const MTPmessages_StickerSetInstallResult &result) { + const auto controller = weak.get(); + if (!controller) { + return; + } + if (result.type() == mtpc_messages_stickerSetInstallResultSuccess) { + Ui::Toast::Show(tr::lng_stickers_has_been_archived(tr::now)); + + const auto &session = controller->session(); + auto &order = session.data().stickers().setsOrderRef(); + const auto index = order.indexOf(setId); + if (index == -1) { + return; + } + order.removeAt(index); + + session.local().writeInstalledStickers(); + session.local().writeArchivedStickers(); + + session.data().stickers().notifyUpdated(); + } + }).fail([](const MTP::Error &error) { + Ui::Toast::Show(Lang::Hard::ServerError()); + }).send(); +} + void StickerSetBox::updateTitleAndButtons() { setTitle(_inner->title()); updateButtons(); @@ -237,6 +269,24 @@ void StickerSetBox::updateButtons() { }; addButton(tr::lng_stickers_share_pack(), std::move(share)); addButton(tr::lng_cancel(), [=] { closeBox(); }); + + if (!_inner->shortName().isEmpty()) { + const auto top = addTopButton(st::infoTopBarMenu); + const auto archive = [=] { + archiveStickers(); + closeBox(); + }; + const auto menu = + std::make_shared>(); + top->setClickedCallback([=] { + *menu = base::make_unique_q(top); + (*menu)->addAction( + tr::lng_stickers_archive_pack(tr::now), + archive); + (*menu)->popup(QCursor::pos()); + return true; + }); + } } } else { addButton(tr::lng_cancel(), [=] { closeBox(); }); diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.h b/Telegram/SourceFiles/boxes/sticker_set_box.h index b480f5885..a5c105531 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.h +++ b/Telegram/SourceFiles/boxes/sticker_set_box.h @@ -42,6 +42,7 @@ private: void updateButtons(); void addStickers(); void copyStickersLink(); + void archiveStickers(); const not_null _controller; MTPInputStickerSet _set; From 383acf0ffc489990302797ff0f048519b3c7c9bf Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 May 2021 12:10:55 +0400 Subject: [PATCH 041/477] Fix crash in native notifications on Linux. --- .../platform/linux/notifications_manager_linux.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index e5a8a27ff..82955b01c 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -578,8 +578,9 @@ void NotificationData::show() { || _hints.find(_imageKey) == end(_hints) ? Glib::ustring(GetIconName().toStdString()) : Glib::ustring(); + const auto connection = _dbusConnection; - _dbusConnection->call( + connection->call( std::string(kObjectPath), std::string(kInterface), "Notify", @@ -595,7 +596,7 @@ void NotificationData::show() { }), [=](const Glib::RefPtr &result) { try { - auto reply = _dbusConnection->call_finish(result); + auto reply = connection->call_finish(result); const auto notificationId = GlibVariantCast( reply.get_child(0)); crl::on_main(weak, [=] { From 2d8f43bd8cec731424376aaddb08ac64d1b3eebc Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 May 2021 21:40:20 +0400 Subject: [PATCH 042/477] Fix text color for crash report window. --- Telegram/SourceFiles/core/crash_report_window.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Telegram/SourceFiles/core/crash_report_window.cpp b/Telegram/SourceFiles/core/crash_report_window.cpp index d3f75ae6c..da086d008 100644 --- a/Telegram/SourceFiles/core/crash_report_window.cpp +++ b/Telegram/SourceFiles/core/crash_report_window.cpp @@ -80,6 +80,7 @@ PreLaunchLabel::PreLaunchLabel(QWidget *parent) : QLabel(parent) { QPalette p(palette()); p.setColor(QPalette::WindowText, QColor(0, 0, 0)); + p.setColor(QPalette::Text, QColor(0, 0, 0)); setPalette(p); show(); }; @@ -98,6 +99,7 @@ PreLaunchInput::PreLaunchInput(QWidget *parent, bool password) : QLineEdit(paren QPalette p(palette()); p.setColor(QPalette::WindowText, QColor(0, 0, 0)); + p.setColor(QPalette::Text, QColor(0, 0, 0)); setPalette(p); QLineEdit::setTextMargins(0, 0, 0, 0); @@ -116,6 +118,7 @@ PreLaunchLog::PreLaunchLog(QWidget *parent) : QTextEdit(parent) { QPalette p(palette()); p.setColor(QPalette::WindowText, QColor(96, 96, 96)); + p.setColor(QPalette::Text, QColor(96, 96, 96)); setPalette(p); setReadOnly(true); @@ -155,6 +158,11 @@ PreLaunchCheckbox::PreLaunchCheckbox(QWidget *parent) : QCheckBox(parent) { closeFont.setPixelSize(static_cast(parent)->basicSize()); setFont(closeFont); + QPalette p(palette()); + p.setColor(QPalette::WindowText, QColor(96, 96, 96)); + p.setColor(QPalette::Text, QColor(96, 96, 96)); + setPalette(p); + setCursor(Qt::PointingHandCursor); show(); }; From e7ca35a276a40f38f3c91d9af3f80fa4bb658b1e Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 May 2021 14:48:44 +0400 Subject: [PATCH 043/477] Fix possible deadlock in debug logs. --- Telegram/SourceFiles/core/launcher.cpp | 6 ++---- Telegram/SourceFiles/logs.cpp | 19 +++++++++++++++++++ Telegram/SourceFiles/logs.h | 1 + 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/core/launcher.cpp b/Telegram/SourceFiles/core/launcher.cpp index 85079a923..3748be153 100644 --- a/Telegram/SourceFiles/core/launcher.cpp +++ b/Telegram/SourceFiles/core/launcher.cpp @@ -420,7 +420,6 @@ void Launcher::prepareSettings() { void Launcher::initQtMessageLogging() { static QtMessageHandler OriginalMessageHandler = nullptr; - static bool WritingQtMessage = false; OriginalMessageHandler = qInstallMessageHandler([]( QtMsgType type, const QMessageLogContext &context, @@ -429,10 +428,9 @@ void Launcher::initQtMessageLogging() { OriginalMessageHandler(type, context, msg); } if (Logs::DebugEnabled() || !Logs::started()) { - if (!WritingQtMessage) { - WritingQtMessage = true; + if (!Logs::WritingEntry()) { + // Sometimes Qt logs something inside our own logging. LOG((msg)); - WritingQtMessage = false; } } }); diff --git a/Telegram/SourceFiles/logs.cpp b/Telegram/SourceFiles/logs.cpp index 7ad8bbbda..d8c7adf3c 100644 --- a/Telegram/SourceFiles/logs.cpp +++ b/Telegram/SourceFiles/logs.cpp @@ -14,6 +14,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { std::atomic ThreadCounter/* = 0*/; +thread_local bool WritingEntryFlag/* = false*/; + +class WritingEntryScope final { +public: + WritingEntryScope() { + WritingEntryFlag = true; + } + ~WritingEntryScope() { + WritingEntryFlag = false; + } +}; } // namespace @@ -73,6 +84,8 @@ public: void closeMain() { QMutexLocker lock(_logsMutex(LogDataMain)); + WritingEntryScope scope; + const auto file = files[LogDataMain].get(); if (file && file->isOpen()) { file->close(); @@ -98,6 +111,8 @@ public: void write(LogDataType type, const QString &msg) { QMutexLocker lock(_logsMutex(type)); + WritingEntryScope scope; + if (type != LogDataMain) { reopenDebug(); } @@ -323,6 +338,10 @@ bool DebugEnabled() { #endif } +bool WritingEntry() { + return WritingEntryFlag; +} + void start(not_null launcher) { Assert(LogsData == nullptr); diff --git a/Telegram/SourceFiles/logs.h b/Telegram/SourceFiles/logs.h index e42bf8b22..f45b07658 100644 --- a/Telegram/SourceFiles/logs.h +++ b/Telegram/SourceFiles/logs.h @@ -19,6 +19,7 @@ namespace Logs { void SetDebugEnabled(bool enabled); bool DebugEnabled(); +[[nodiscard]] bool WritingEntry(); void start(not_null launcher); bool started(); From 71deaa48afab2bcf9df67b9b347b1f44aad3a9ce Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 May 2021 15:17:54 +0400 Subject: [PATCH 044/477] Don't crash on voice recording error. Fixes #16217. --- .../media/audio/media_audio_capture.cpp | 89 +++++++++++-------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp index a5965e0d1..deb2474d7 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp @@ -47,17 +47,17 @@ public: void start(Fn updated, Fn error); void stop(Fn callback = nullptr); - void timeout(); - private: - void processFrame(int32 offset, int32 framesize); + void process(); + + [[nodiscard]] bool processFrame(int32 offset, int32 framesize); void fail(); - void writeFrame(AVFrame *frame); + [[nodiscard]] bool writeFrame(AVFrame *frame); // Writes the packets till EAGAIN is got from av_receive_packet() // Returns number of packets written or -1 on error - int writePackets(); + [[nodiscard]] int writePackets(); Fn _updated; Fn _error; @@ -150,6 +150,7 @@ struct Instance::Inner::Private { AVCodec *codec = nullptr; AVCodecContext *codecContext = nullptr; bool opened = false; + bool processing = false; int srcSamples = 0; int dstSamples = 0; @@ -217,7 +218,7 @@ struct Instance::Inner::Private { Instance::Inner::Inner(QThread *thread) : d(std::make_unique()) -, _timer(thread, [=] { timeout(); }) { +, _timer(thread, [=] { process(); }) { moveToThread(thread); } @@ -226,10 +227,10 @@ Instance::Inner::~Inner() { } void Instance::Inner::fail() { - Expects(_error != nullptr); - stop(); - _error(); + if (const auto error = base::take(_error)) { + InvokeQueued(this, error); + } } void Instance::Inner::start(Fn updated, Fn error) { @@ -384,13 +385,21 @@ void Instance::Inner::stop(Fn callback) { } _timer.cancel(); - if (d->device) { + const auto needResult = (callback != nullptr); + const auto hadDevice = (d->device != nullptr); + if (hadDevice) { alcCaptureStop(d->device); - timeout(); // get last data + if (d->processing) { + Assert(!needResult); // stop in the middle of processing - error. + } else { + process(); // get last data + } + alcCaptureCloseDevice(d->device); + d->device = nullptr; } // Write what is left - if (!_captured.isEmpty()) { + if (needResult && !_captured.isEmpty()) { auto fadeSamples = kCaptureFadeInDuration * kCaptureFrequency / 1000; auto capturedSamples = static_cast(_captured.size() / sizeof(short)); if ((_captured.size() % sizeof(short)) || (d->fullSamples + capturedSamples < kCaptureFrequency) || (capturedSamples < fadeSamples)) { @@ -414,11 +423,13 @@ void Instance::Inner::stop(Fn callback) { int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0; while (_captured.size() >= encoded + framesize) { - processFrame(encoded, framesize); + if (!processFrame(encoded, framesize)) { + break; + } encoded += framesize; } - writeFrame(nullptr); // drain the codec - if (encoded != _captured.size()) { + // Drain the codec. + if (!writeFrame(nullptr) || encoded != _captured.size()) { d->fullSamples = 0; d->dataPos = 0; d->data.clear(); @@ -436,14 +447,14 @@ void Instance::Inner::stop(Fn callback) { _captured = QByteArray(); // Finish stream - if (d->device) { + if (needResult && hadDevice) { av_write_trailer(d->fmtContext); } QByteArray result = d->fullSamples ? d->data : QByteArray(); VoiceWaveform waveform; qint32 samples = d->fullSamples; - if (samples && !d->waveform.isEmpty()) { + if (needResult && samples && !d->waveform.isEmpty()) { int64 count = d->waveform.size(), sum = 0; if (count >= Player::kWaveformSamplesCount) { QVector peaks; @@ -472,11 +483,7 @@ void Instance::Inner::stop(Fn callback) { } } } - if (d->device) { - alcCaptureStop(d->device); - alcCaptureCloseDevice(d->device); - d->device = nullptr; - + if (hadDevice) { if (d->codecContext) { avcodec_free_context(&d->codecContext); d->codecContext = nullptr; @@ -528,12 +535,17 @@ void Instance::Inner::stop(Fn callback) { d->waveform.clear(); } - if (callback) { + if (needResult) { callback({ result, waveform, samples }); } } -void Instance::Inner::timeout() { +void Instance::Inner::process() { + Expects(!d->processing); + + d->processing = true; + const auto guard = gsl::finally([&] { d->processing = false; }); + if (!d->device) { _timer.cancel(); return; @@ -582,7 +594,9 @@ void Instance::Inner::timeout() { // Write frames int32 framesize = d->srcSamples * d->codecContext->channels * sizeof(short), encoded = 0; while (uint32(_captured.size()) >= encoded + framesize + fadeSamples * sizeof(short)) { - processFrame(encoded, framesize); + if (!processFrame(encoded, framesize)) { + return; + } encoded += framesize; } @@ -597,13 +611,13 @@ void Instance::Inner::timeout() { } } -void Instance::Inner::processFrame(int32 offset, int32 framesize) { +bool Instance::Inner::processFrame(int32 offset, int32 framesize) { // Prepare audio frame if (framesize % sizeof(short)) { // in the middle of a sample LOG(("Audio Error: Bad framesize in writeFrame() for capture, framesize %1, %2").arg(framesize)); fail(); - return; + return false; } auto samplesCnt = static_cast(framesize / sizeof(short)); @@ -650,7 +664,7 @@ void Instance::Inner::processFrame(int32 offset, int32 framesize) { if ((res = av_samples_alloc(d->dstSamplesData, 0, d->codecContext->channels, d->dstSamples, d->codecContext->sample_fmt, 1)) < 0) { LOG(("Audio Error: Unable to av_samples_alloc for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); fail(); - return; + return false; } d->dstSamplesSize = av_samples_get_buffer_size(0, d->codecContext->channels, d->maxDstSamples, d->codecContext->sample_fmt, 0); } @@ -658,7 +672,7 @@ void Instance::Inner::processFrame(int32 offset, int32 framesize) { if ((res = swr_convert(d->swrContext, d->dstSamplesData, d->dstSamples, (const uint8_t **)srcSamplesData, d->srcSamples)) < 0) { LOG(("Audio Error: Unable to swr_convert for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); fail(); - return; + return false; } // Write audio frame @@ -670,45 +684,50 @@ void Instance::Inner::processFrame(int32 offset, int32 framesize) { avcodec_fill_audio_frame(frame, d->codecContext->channels, d->codecContext->sample_fmt, d->dstSamplesData[0], d->dstSamplesSize, 0); - writeFrame(frame); + if (!writeFrame(frame)) { + return false; + } d->fullSamples += samplesCnt; av_frame_free(&frame); + return true; } -void Instance::Inner::writeFrame(AVFrame *frame) { +bool Instance::Inner::writeFrame(AVFrame *frame) { int res = 0; char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; res = avcodec_send_frame(d->codecContext, frame); if (res == AVERROR(EAGAIN)) { - int packetsWritten = writePackets(); + const auto packetsWritten = writePackets(); if (packetsWritten < 0) { if (frame && packetsWritten == AVERROR_EOF) { LOG(("Audio Error: EOF in packets received when EAGAIN was got in avcodec_send_frame()")); fail(); } - return; + return false; } else if (!packetsWritten) { LOG(("Audio Error: No packets received when EAGAIN was got in avcodec_send_frame()")); fail(); - return; + return false; } res = avcodec_send_frame(d->codecContext, frame); } if (res < 0) { LOG(("Audio Error: Unable to avcodec_send_frame for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res))); fail(); - return; + return false; } if (!frame) { // drain if ((res = writePackets()) != AVERROR_EOF) { LOG(("Audio Error: not EOF in packets received when draining the codec, result %1").arg(res)); fail(); + return false; } } + return true; } int Instance::Inner::writePackets() { From a8807bc915f2439acc7c84f06d931d96d6ca602a Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 May 2021 15:33:42 +0400 Subject: [PATCH 045/477] Fix voice recoding with FFmpeg 4.4. Fixes #16217. --- Telegram/SourceFiles/media/audio/media_audio_capture.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp index deb2474d7..d12916878 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp @@ -679,6 +679,10 @@ bool Instance::Inner::processFrame(int32 offset, int32 framesize) { AVFrame *frame = av_frame_alloc(); + frame->format = d->codecContext->sample_fmt; + frame->channels = d->codecContext->channels; + frame->channel_layout = d->codecContext->channel_layout; + frame->sample_rate = d->codecContext->sample_rate; frame->nb_samples = d->dstSamples; frame->pts = av_rescale_q(d->fullSamples, AVRational { 1, d->codecContext->sample_rate }, d->codecContext->time_base); From 57b39823469581e7896dbcc2f3cf184a86f494ed Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 May 2021 15:22:39 +0400 Subject: [PATCH 046/477] Update tg_owt in Snap build. --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 61dc8c3b3..c375801af 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -424,7 +424,7 @@ parts: webrtc: source: https://github.com/desktop-app/tg_owt.git source-depth: 1 - source-commit: 7f965710b93c4dadd7e6f1ac739e708694df7929 + source-commit: 18cb4cd9bb4c2f5f5f5e760ec808f74c302bc1bf plugin: cmake build-packages: - yasm From 3722486b19acd5c5d107a97e74d4c077074b4004 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 15 May 2021 23:38:42 +0400 Subject: [PATCH 047/477] Add new tg_owt dependencies to snap --- snap/snapcraft.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c375801af..5803d3aa4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -428,11 +428,31 @@ parts: plugin: cmake build-packages: - yasm + - libglib2.0-dev - libopus-dev + - libpipewire-0.2-dev - libssl-dev + - libx11-dev + - libxcomposite-dev + - libxdamage-dev + - libxext-dev + - libxfixes-dev + - libxrender-dev + - libxrandr-dev + - libxtst-dev stage-packages: + - libglib2.0-0 - libopus0 + - libpipewire-0.2-1 - libssl1.1 + - libx11-6 + - libxcomposite1 + - libxdamage1 + - libxext6 + - libxfixes3 + - libxrender1 + - libxrandr2 + - libxtst6 cmake-parameters: - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_INSTALL_PREFIX=/usr From 3cb9312805c81d0010c83395955ce91695c47c68 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 16 May 2021 12:24:33 +0400 Subject: [PATCH 048/477] NEON support for ARMv7 is fixed in tg_owt --- snap/snapcraft.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 5803d3aa4..c31e0ab3b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -459,8 +459,6 @@ parts: - -DBUILD_SHARED_LIBS=OFF - -DJPEG_LIBRARY_RELEASE=$SNAPCRAFT_STAGE/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libjpeg.so - -DJPEG_INCLUDE_DIR=$SNAPCRAFT_STAGE/usr/include - # NEON support for arm 32-bit is broken and no one seem to fix it - - -DTG_OWT_ARCH_ARMV7_USE_NEON=OFF prime: [-./*] after: - ffmpeg From f7489592d6518a98a70a890dedbabb359e5da79a Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 15 May 2021 10:51:47 +0400 Subject: [PATCH 049/477] Fix BIO_new_mem_buf leaks --- Telegram/SourceFiles/_other/packer.cpp | 39 +++++++++++++++++-- Telegram/SourceFiles/core/update_checker.cpp | 39 +++++++++++++++++-- .../details/mtproto_rsa_public_key.cpp | 18 ++++++--- 3 files changed, 85 insertions(+), 11 deletions(-) diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 99fae21f1..630c0b940 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -47,6 +47,18 @@ typedef signed int int32; namespace{ +struct BIODeleter { + void operator()(BIO *value) { + BIO_free(value); + } +}; + +inline auto makeBIO(const void *buf, int len) { + return std::unique_ptr{ + BIO_new_mem_buf(buf, len), + }; +} + inline uint32 sha1Shift(uint32 v, uint32 shift) { return ((v << shift) | (v >> (32 - shift))); } @@ -430,7 +442,15 @@ int main(int argc, char *argv[]) uint32 siglen = 0; cout << "Signing..\n"; - RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast((BetaChannel || AlphaVersion) ? PrivateBetaKey : PrivateKey), -1), 0, 0, 0); + RSA *prKey = [] { + const auto bio = makeBIO( + const_cast( + (BetaChannel || AlphaVersion) + ? PrivateBetaKey + : PrivateKey), + -1); + return PEM_read_bio_RSAPrivateKey(bio.get(), 0, 0, 0); + }(); if (!prKey) { cout << "Could not read RSA private key!\n"; return -1; @@ -453,7 +473,15 @@ int main(int argc, char *argv[]) } cout << "Checking signature..\n"; - RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast((BetaChannel || AlphaVersion) ? PublicBetaKey : PublicKey), -1), 0, 0, 0); + RSA *pbKey = [] { + const auto bio = makeBIO( + const_cast( + (BetaChannel || AlphaVersion) + ? PublicBetaKey + : PublicKey), + -1); + return PEM_read_bio_RSAPublicKey(bio.get(), 0, 0, 0); + }(); if (!pbKey) { cout << "Could not read RSA public key!\n"; return -1; @@ -510,7 +538,12 @@ QString countAlphaVersionSignature(quint64 version) { // duplicated in autoupdat uint32 siglen = 0; - RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast(cAlphaPrivateKey.constData()), -1), 0, 0, 0); + RSA *prKey = [] { + const auto bio = makeBIO( + const_cast(cAlphaPrivateKey.constData()), + -1); + return PEM_read_bio_RSAPrivateKey(bio.get(), 0, 0, 0); + }(); if (!prKey) { cout << "Error: Could not read alpha private key!\n"; return QString(); diff --git a/Telegram/SourceFiles/core/update_checker.cpp b/Telegram/SourceFiles/core/update_checker.cpp index 5f42cfb75..f621f164a 100644 --- a/Telegram/SourceFiles/core/update_checker.cpp +++ b/Telegram/SourceFiles/core/update_checker.cpp @@ -76,6 +76,18 @@ using VersionChar = wchar_t; using Loader = MTP::AbstractDedicatedLoader; +struct BIODeleter { + void operator()(BIO *value) { + BIO_free(value); + } +}; + +inline auto MakeBIO(const void *buf, int len) { + return std::unique_ptr{ + BIO_new_mem_buf(buf, len), + }; +} + class Checker : public base::has_weak_ptr { public: Checker(bool testing); @@ -289,7 +301,15 @@ bool UnpackUpdate(const QString &filepath) { return false; } - RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast(AppBetaVersion ? UpdatesPublicBetaKey : UpdatesPublicKey), -1), 0, 0, 0); + RSA *pbKey = [] { + const auto bio = MakeBIO( + const_cast( + AppBetaVersion + ? UpdatesPublicBetaKey + : UpdatesPublicKey), + -1); + return PEM_read_bio_RSAPublicKey(bio.get(), 0, 0, 0); + }(); if (!pbKey) { LOG(("Update Error: cant read public rsa key!")); return false; @@ -298,7 +318,15 @@ bool UnpackUpdate(const QString &filepath) { RSA_free(pbKey); // try other public key, if we update from beta to stable or vice versa - pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast(AppBetaVersion ? UpdatesPublicKey : UpdatesPublicBetaKey), -1), 0, 0, 0); + pbKey = [] { + const auto bio = MakeBIO( + const_cast( + AppBetaVersion + ? UpdatesPublicKey + : UpdatesPublicBetaKey), + -1); + return PEM_read_bio_RSAPublicKey(bio.get(), 0, 0, 0); + }(); if (!pbKey) { LOG(("Update Error: cant read public rsa key!")); return false; @@ -1649,7 +1677,12 @@ QString countAlphaVersionSignature(uint64 version) { // duplicated in packer.cpp uint32 siglen = 0; - RSA *prKey = PEM_read_bio_RSAPrivateKey(BIO_new_mem_buf(const_cast(cAlphaPrivateKey().constData()), -1), 0, 0, 0); + RSA *prKey = [] { + const auto bio = MakeBIO( + const_cast(cAlphaPrivateKey().constData()), + -1); + return PEM_read_bio_RSAPrivateKey(bio.get(), 0, 0, 0); + }(); if (!prKey) { LOG(("Error: Could not read alpha private key!")); return QString(); diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_rsa_public_key.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_rsa_public_key.cpp index 8d47ed822..a053a18ac 100644 --- a/Telegram/SourceFiles/mtproto/details/mtproto_rsa_public_key.cpp +++ b/Telegram/SourceFiles/mtproto/details/mtproto_rsa_public_key.cpp @@ -54,6 +54,12 @@ enum class Format { Unknown, }; +struct BIODeleter { + void operator()(BIO *value) { + BIO_free(value); + } +}; + Format GuessFormat(bytes::const_span key) { const auto array = QByteArray::fromRawData( reinterpret_cast(key.data()), @@ -68,14 +74,16 @@ Format GuessFormat(bytes::const_span key) { RSA *CreateRaw(bytes::const_span key) { const auto format = GuessFormat(key); - const auto bio = BIO_new_mem_buf( - const_cast(key.data()), - key.size()); + const auto bio = std::unique_ptr{ + BIO_new_mem_buf( + const_cast(key.data()), + key.size()), + }; switch (format) { case Format::RSAPublicKey: - return PEM_read_bio_RSAPublicKey(bio, nullptr, nullptr, nullptr); + return PEM_read_bio_RSAPublicKey(bio.get(), nullptr, nullptr, nullptr); case Format::RSA_PUBKEY: - return PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr); + return PEM_read_bio_RSA_PUBKEY(bio.get(), nullptr, nullptr, nullptr); } Unexpected("format in RSAPublicKey::Private::Create."); } From 17c514e85155a5bd79644929c44f82ac9ec3453a Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 13 May 2021 08:39:16 +0400 Subject: [PATCH 050/477] Use surface as XdgExporter parent --- .../platform/linux/linux_wayland_integration.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp index 6586988d8..d2ca5633b 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/platform/base_platform_info.h" -#include #include #include #include @@ -92,10 +91,10 @@ WaylandIntegration::Private::Private() }; connect( - QCoreApplication::instance(), - &QCoreApplication::aboutToQuit, - this, - [=] { _xdgExporter = nullptr; }); + _applicationConnection, + &ConnectionThread::connectionDied, + _xdgExporter.get(), + &XdgExporter::destroy); }); _connection.initConnection(); @@ -130,7 +129,7 @@ QString WaylandIntegration::nativeHandle(QWindow *window) { if (const auto surface = Surface::fromWindow(window)) { if (const auto exported = exporter->exportTopLevel( surface, - _private->xdgExporter())) { + surface)) { QEventLoop loop; QObject::connect( exported, From 166c28c215c09154035c1104c7b0d1c8f4cf5546 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 13 May 2021 14:34:24 +0400 Subject: [PATCH 051/477] Get rid of default_delete --- .../SourceFiles/platform/linux/linux_wayland_integration.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp index d2ca5633b..33eecd74c 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -85,9 +85,8 @@ WaylandIntegration::Private::Private() &_applicationRegistry, &Registry::exporterUnstableV2Announced, [=](uint name, uint version) { - _xdgExporter = { + _xdgExporter = std::unique_ptr{ _applicationRegistry.createXdgExporter(name, version), - std::default_delete(), }; connect( From 434ef343789fd71166caa8c67fd57652580c348b Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 13 May 2021 06:38:30 +0400 Subject: [PATCH 052/477] Implement taskbar icon hidding on Wayland with org_kde_plasma_shell protocol --- .../linux/linux_wayland_integration.cpp | 44 +++++++++++++++++++ .../linux/linux_wayland_integration.h | 2 + .../linux/linux_wayland_integration_dummy.cpp | 7 +++ .../platform/linux/main_window_linux.cpp | 27 +++++++----- .../platform/linux/specific_linux.cpp | 10 ++++- 5 files changed, 76 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp index 33eecd74c..ba521184b 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include using namespace KWayland::Client; @@ -31,6 +32,10 @@ public: return _xdgExporter.get(); } + [[nodiscard]] PlasmaShell *plasmaShell() { + return _plasmaShell.get(); + } + [[nodiscard]] QEventLoop &interfacesLoop() { return _interfacesLoop; } @@ -45,6 +50,7 @@ private: Registry _registry; Registry _applicationRegistry; std::unique_ptr _xdgExporter; + std::unique_ptr _plasmaShell; QEventLoop _interfacesLoop; bool _interfacesAnnounced = false; }; @@ -96,6 +102,21 @@ WaylandIntegration::Private::Private() &XdgExporter::destroy); }); + connect( + &_applicationRegistry, + &Registry::plasmaShellAnnounced, + [=](uint name, uint version) { + _plasmaShell = std::unique_ptr{ + _applicationRegistry.createPlasmaShell(name, version), + }; + + connect( + _applicationConnection, + &ConnectionThread::connectionDied, + _plasmaShell.get(), + &PlasmaShell::destroy); + }); + _connection.initConnection(); } @@ -143,5 +164,28 @@ QString WaylandIntegration::nativeHandle(QWindow *window) { return {}; } +bool WaylandIntegration::skipTaskbarSupported() { + return _private->plasmaShell(); +} + +void WaylandIntegration::skipTaskbar(QWindow *window, bool skip) { + const auto shell = _private->plasmaShell(); + if (!shell) { + return; + } + + const auto surface = Surface::fromWindow(window); + if (!surface) { + return; + } + + const auto plasmaSurface = shell->createSurface(surface, surface); + if (!plasmaSurface) { + return; + } + + plasmaSurface->setSkipTaskbar(skip); +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h index a41d34209..d762ef9f9 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h @@ -16,6 +16,8 @@ public: void waitForInterfaceAnnounce(); bool supportsXdgDecoration(); QString nativeHandle(QWindow *window); + bool skipTaskbarSupported(); + void skipTaskbar(QWindow *window, bool skip); private: WaylandIntegration(); diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp index 2b4068553..74e6485dc 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp @@ -37,5 +37,12 @@ QString WaylandIntegration::nativeHandle(QWindow *window) { return {}; } +bool WaylandIntegration::skipTaskbarSupported() { + return false; +} + +void WaylandIntegration::skipTaskbar(QWindow *window, bool skip) { +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index c6a9f6dae..7b54cc701 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_window.h" #include "platform/linux/specific_linux.h" +#include "platform/linux/linux_wayland_integration.h" #include "history/history.h" #include "history/history_widget.h" #include "history/history_inner_widget.h" @@ -59,6 +60,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Platform { namespace { +using internal::WaylandIntegration; + constexpr auto kPanelTrayIconName = "telegram-panel"_cs; constexpr auto kMutePanelTrayIconName = "telegram-mute-panel"_cs; constexpr auto kAttentionPanelTrayIconName = "telegram-attention-panel"_cs; @@ -83,15 +86,15 @@ QIcon TrayIcon; QString TrayIconThemeName, TrayIconName; #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION -bool XCBSkipTaskbar(QWindow *window, bool set) { +void XCBSkipTaskbar(QWindow *window, bool skip) { const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { - return false; + return; } const auto root = base::Platform::XCB::GetRootWindowFromQt(); if (!root.has_value()) { - return false; + return; } const auto stateAtom = base::Platform::XCB::GetAtom( @@ -99,7 +102,7 @@ bool XCBSkipTaskbar(QWindow *window, bool set) { "_NET_WM_STATE"); if (!stateAtom.has_value()) { - return false; + return; } const auto skipTaskbarAtom = base::Platform::XCB::GetAtom( @@ -107,7 +110,7 @@ bool XCBSkipTaskbar(QWindow *window, bool set) { "_NET_WM_STATE_SKIP_TASKBAR"); if (!skipTaskbarAtom.has_value()) { - return false; + return; } xcb_client_message_event_t xev; @@ -116,7 +119,7 @@ bool XCBSkipTaskbar(QWindow *window, bool set) { xev.sequence = 0; xev.window = window->winId(); xev.format = 32; - xev.data.data32[0] = set ? 1 : 0; + xev.data.data32[0] = skip ? 1 : 0; xev.data.data32[1] = *skipTaskbarAtom; xev.data.data32[2] = 0; xev.data.data32[3] = 0; @@ -129,19 +132,19 @@ bool XCBSkipTaskbar(QWindow *window, bool set) { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, reinterpret_cast(&xev)); - - return true; } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION -bool SkipTaskbar(QWindow *window, bool set) { +void SkipTaskbar(QWindow *window, bool skip) { + if (const auto integration = WaylandIntegration::Instance()) { + integration->skipTaskbar(window, skip); + } + #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION if (IsX11()) { - return XCBSkipTaskbar(window, set); + XCBSkipTaskbar(window, skip); } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION - - return false; } QString GetPanelIconName(int counter, bool muted) { diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 971bf3a50..498b37372 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -644,9 +644,15 @@ bool TrayIconSupported() { } bool SkipTaskbarSupported() { + if (const auto integration = WaylandIntegration::Instance()) { + return integration->skipTaskbarSupported(); + } + #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION - return IsX11() - && base::Platform::XCB::IsSupportedByWM("_NET_WM_STATE_SKIP_TASKBAR"); + if (IsX11()) { + return base::Platform::XCB::IsSupportedByWM( + "_NET_WM_STATE_SKIP_TASKBAR"); + } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION return false; From 0b86feeeb58a0a5302a6811f0f51e97c562a7134 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 13 May 2021 08:16:21 +0400 Subject: [PATCH 053/477] Implement appmenu on Wayland with org_kde_kwin_appmenu protocol --- .../linux/linux_wayland_integration.cpp | 43 +++++++++++++++++++ .../linux/linux_wayland_integration.h | 4 ++ .../linux/linux_wayland_integration_dummy.cpp | 6 +++ .../platform/linux/main_window_linux.cpp | 34 ++++++++++----- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp index ba521184b..16db763d8 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include using namespace KWayland::Client; @@ -36,6 +37,10 @@ public: return _plasmaShell.get(); } + [[nodiscard]] AppMenuManager *appMenuManager() { + return _appMenuManager.get(); + } + [[nodiscard]] QEventLoop &interfacesLoop() { return _interfacesLoop; } @@ -51,6 +56,7 @@ private: Registry _applicationRegistry; std::unique_ptr _xdgExporter; std::unique_ptr _plasmaShell; + std::unique_ptr _appMenuManager; QEventLoop _interfacesLoop; bool _interfacesAnnounced = false; }; @@ -117,6 +123,21 @@ WaylandIntegration::Private::Private() &PlasmaShell::destroy); }); + connect( + &_applicationRegistry, + &Registry::appMenuAnnounced, + [=](uint name, uint version) { + _appMenuManager = std::unique_ptr{ + _applicationRegistry.createAppMenuManager(name, version), + }; + + connect( + _applicationConnection, + &ConnectionThread::connectionDied, + _appMenuManager.get(), + &AppMenuManager::destroy); + }); + _connection.initConnection(); } @@ -187,5 +208,27 @@ void WaylandIntegration::skipTaskbar(QWindow *window, bool skip) { plasmaSurface->setSkipTaskbar(skip); } +void WaylandIntegration::registerAppMenu( + QWindow *window, + const QString &serviceName, + const QString &objectPath) { + const auto manager = _private->appMenuManager(); + if (!manager) { + return; + } + + const auto surface = Surface::fromWindow(window); + if (!surface) { + return; + } + + const auto appMenu = manager->create(surface, surface); + if (!appMenu) { + return; + } + + appMenu->setAddress(serviceName, objectPath); +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h index d762ef9f9..dbf47fec1 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h @@ -18,6 +18,10 @@ public: QString nativeHandle(QWindow *window); bool skipTaskbarSupported(); void skipTaskbar(QWindow *window, bool skip); + void registerAppMenu( + QWindow *window, + const QString &serviceName, + const QString &objectPath); private: WaylandIntegration(); diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp index 74e6485dc..7ab26b1e2 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration_dummy.cpp @@ -44,5 +44,11 @@ bool WaylandIntegration::skipTaskbarSupported() { void WaylandIntegration::skipTaskbar(QWindow *window, bool skip) { } +void WaylandIntegration::registerAppMenu( + QWindow *window, + const QString &serviceName, + const QString &objectPath) { +} + } // namespace internal } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 7b54cc701..b409f1597 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -503,7 +503,15 @@ bool IsAppMenuSupported() { // This call must be made from the same bus connection as DBusMenuExporter // So it must use QDBusConnection -void RegisterAppMenu(uint winId, const QString &menuPath) { +void RegisterAppMenu(QWindow *window, const QString &menuPath) { + if (const auto integration = WaylandIntegration::Instance()) { + integration->registerAppMenu( + window, + QDBusConnection::sessionBus().baseService(), + menuPath); + return; + } + auto message = QDBusMessage::createMethodCall( kAppMenuService.utf16(), kAppMenuObjectPath.utf16(), @@ -511,7 +519,7 @@ void RegisterAppMenu(uint winId, const QString &menuPath) { qsl("RegisterWindow")); message.setArguments({ - winId, + window->winId(), QVariant::fromValue(QDBusObjectPath(menuPath)) }); @@ -520,7 +528,11 @@ void RegisterAppMenu(uint winId, const QString &menuPath) { // This call must be made from the same bus connection as DBusMenuExporter // So it must use QDBusConnection -void UnregisterAppMenu(uint winId) { +void UnregisterAppMenu(QWindow *window) { + if (const auto integration = WaylandIntegration::Instance()) { + return; + } + auto message = QDBusMessage::createMethodCall( kAppMenuService.utf16(), kAppMenuObjectPath.utf16(), @@ -528,7 +540,7 @@ void UnregisterAppMenu(uint winId) { qsl("UnregisterWindow")); message.setArguments({ - winId + window->winId() }); QDBusConnection::sessionBus().send(message); @@ -806,9 +818,9 @@ void MainWindow::handleAppMenuOwnerChanged( } if (_appMenuSupported && _mainMenuExporter) { - RegisterAppMenu(winId(), kMainMenuObjectPath.utf16()); + RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16()); } else { - UnregisterAppMenu(winId()); + UnregisterAppMenu(windowHandle()); } } #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION @@ -1133,7 +1145,7 @@ void MainWindow::createGlobalMenu() { psMainMenu); if (_appMenuSupported) { - RegisterAppMenu(winId(), kMainMenuObjectPath.utf16()); + RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16()); } updateGlobalMenu(); @@ -1269,9 +1281,11 @@ void MainWindow::handleVisibleChangedHook(bool visible) { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION if (_appMenuSupported && _mainMenuExporter) { if (visible) { - RegisterAppMenu(winId(), kMainMenuObjectPath.utf16()); + base::call_delayed(1, this, [=] { + RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16()); + }); } else { - UnregisterAppMenu(winId()); + UnregisterAppMenu(windowHandle()); } } #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION @@ -1299,7 +1313,7 @@ MainWindow::~MainWindow() { delete _sniTrayIcon; if (_appMenuSupported) { - UnregisterAppMenu(winId()); + UnregisterAppMenu(windowHandle()); } delete _mainMenuExporter; From 30d8894c30ee71e14e289ce1fed94cbafea490b7 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 13 May 2021 14:02:53 +0400 Subject: [PATCH 054/477] Use nodiscard in WaylandIntegration --- .../platform/linux/linux_wayland_integration.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h index dbf47fec1..a37f10e34 100644 --- a/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h +++ b/Telegram/SourceFiles/platform/linux/linux_wayland_integration.h @@ -12,11 +12,11 @@ namespace internal { class WaylandIntegration { public: - static WaylandIntegration *Instance(); + [[nodiscard]] static WaylandIntegration *Instance(); void waitForInterfaceAnnounce(); - bool supportsXdgDecoration(); - QString nativeHandle(QWindow *window); - bool skipTaskbarSupported(); + [[nodiscard]] bool supportsXdgDecoration(); + [[nodiscard]] QString nativeHandle(QWindow *window); + [[nodiscard]] bool skipTaskbarSupported(); void skipTaskbar(QWindow *window, bool skip); void registerAppMenu( QWindow *window, From cfee688feb811004ab7f681aa726ce42a8cde8d7 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 13 May 2021 14:12:05 +0400 Subject: [PATCH 055/477] Replace call_delayed with InvokeQueued in MainWindow::handleVisibleChanged --- Telegram/SourceFiles/platform/linux/main_window_linux.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index b409f1597..99c4e617a 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_session_controller.h" #include "base/platform/base_platform_info.h" -#include "base/call_delayed.h" +#include "base/invoke_queued.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/input_fields.h" #include "facades.h" @@ -1270,7 +1270,7 @@ void MainWindow::updateGlobalMenuHook() { void MainWindow::handleVisibleChangedHook(bool visible) { if (visible) { - base::call_delayed(1, this, [=] { + InvokeQueued(this, [=] { SkipTaskbar( windowHandle(), (Global::WorkMode().value() == dbiwmTrayOnly) @@ -1281,7 +1281,7 @@ void MainWindow::handleVisibleChangedHook(bool visible) { #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION if (_appMenuSupported && _mainMenuExporter) { if (visible) { - base::call_delayed(1, this, [=] { + InvokeQueued(this, [=] { RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16()); }); } else { From 3cf739eca95374e8b50cc4dcdb56aaebea737728 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Thu, 13 May 2021 16:51:16 +0400 Subject: [PATCH 056/477] Use event filter to get surface expose event --- .../platform/linux/main_window_linux.cpp | 43 +++++++++++++------ .../platform/linux/main_window_linux.h | 2 +- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index 99c4e617a..091bd46c6 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -25,7 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "window/window_session_controller.h" #include "base/platform/base_platform_info.h" -#include "base/invoke_queued.h" +#include "base/event_filter.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/input_fields.h" #include "facades.h" @@ -649,6 +649,27 @@ void MainWindow::initHook() { } catch (...) { } + base::install_event_filter(windowHandle(), [=](not_null e) { + if (e->type() == QEvent::Expose) { + auto ee = static_cast(e.get()); + if (ee->region().isNull()) { + return base::EventFilterResult::Continue; + } + if (!windowHandle() + || windowHandle()->parent() + || !windowHandle()->isVisible()) { + return base::EventFilterResult::Continue; + } + handleNativeSurfaceChanged(true); + } else if (e->type() == QEvent::Hide) { + if (!windowHandle() || windowHandle()->parent()) { + return base::EventFilterResult::Continue; + } + handleNativeSurfaceChanged(false); + } + return base::EventFilterResult::Continue; + }); + if (_appMenuSupported) { LOG(("Using D-Bus global menu.")); } else { @@ -1268,22 +1289,18 @@ void MainWindow::updateGlobalMenuHook() { #endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION -void MainWindow::handleVisibleChangedHook(bool visible) { - if (visible) { - InvokeQueued(this, [=] { - SkipTaskbar( - windowHandle(), - (Global::WorkMode().value() == dbiwmTrayOnly) - && trayAvailable()); - }); +void MainWindow::handleNativeSurfaceChanged(bool exist) { + if (exist) { + SkipTaskbar( + windowHandle(), + (Global::WorkMode().value() == dbiwmTrayOnly) + && trayAvailable()); } #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION if (_appMenuSupported && _mainMenuExporter) { - if (visible) { - InvokeQueued(this, [=] { - RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16()); - }); + if (exist) { + RegisterAppMenu(windowHandle(), kMainMenuObjectPath.utf16()); } else { UnregisterAppMenu(windowHandle()); } diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.h b/Telegram/SourceFiles/platform/linux/main_window_linux.h index 04a7b2007..070787cea 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.h +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.h @@ -47,7 +47,6 @@ protected: void initHook() override; void unreadCounterChangedHook() override; void updateGlobalMenuHook() override; - void handleVisibleChangedHook(bool visible) override; void initTrayMenuHook() override; bool hasTrayIcon() const override; @@ -76,6 +75,7 @@ private: base::unique_qptr _trayIconMenuXEmbed; void updateIconCounters(); + void handleNativeSurfaceChanged(bool exist); #ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION StatusNotifierItem *_sniTrayIcon = nullptr; From 837485974a36b4e4e0c0ccfb27073de7743149b5 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sun, 16 May 2021 12:04:48 +0400 Subject: [PATCH 057/477] Use mallocng on Linux --- .gitmodules | 3 +++ Telegram/CMakeLists.txt | 1 + .../SourceFiles/platform/linux/specific_linux.cpp | 11 +++++++++++ Telegram/ThirdParty/mallocng | 1 + cmake | 2 +- 5 files changed, 17 insertions(+), 1 deletion(-) create mode 160000 Telegram/ThirdParty/mallocng diff --git a/.gitmodules b/.gitmodules index f982f14b8..80bc6b96d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -91,3 +91,6 @@ [submodule "Telegram/lib_webview"] path = Telegram/lib_webview url = https://github.com/desktop-app/lib_webview.git +[submodule "Telegram/ThirdParty/mallocng"] + path = Telegram/ThirdParty/mallocng + url = https://github.com/desktop-app/mallocng.git diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index cd620a43a..d3193cb90 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -89,6 +89,7 @@ elseif (LINUX) PRIVATE desktop-app::external_glibmm desktop-app::external_glib + desktop-app::external_mallocng ) if (NOT DESKTOP_APP_DISABLE_DBUS_INTEGRATION) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 498b37372..6bc833ac0 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -59,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include +#include #include @@ -745,6 +746,16 @@ int psFixPrevious() { namespace Platform { void start() { + // avoid stripping custom allocator + malloc(0); + calloc(0, 0); + realloc(nullptr, 0); + free(nullptr); + aligned_alloc(0, 0); + malloc_usable_size(nullptr); + memalign(0, 0); + posix_memalign(nullptr, 0, 0); + LOG(("Launcher filename: %1").arg(QGuiApplication::desktopFileName())); qputenv("PULSE_PROP_application.name", AppName.utf8()); diff --git a/Telegram/ThirdParty/mallocng b/Telegram/ThirdParty/mallocng new file mode 160000 index 000000000..5db0b72cd --- /dev/null +++ b/Telegram/ThirdParty/mallocng @@ -0,0 +1 @@ +Subproject commit 5db0b72cd85b20aec790fccdd31c17111afc7c94 diff --git a/cmake b/cmake index 1720a174c..5318ac7c5 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 1720a174c4d96bc1512837adcae257c6637438e5 +Subproject commit 5318ac7c54dbf33b17ccc4bd3281bb8b8db25e02 From 8e02c50f7dadb5d6a1c7ee084a2500f4e78b592d Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 May 2021 13:07:39 +0400 Subject: [PATCH 058/477] Fix build on Linux. --- Telegram/ThirdParty/hime | 2 +- Telegram/lib_base | 2 +- cmake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/ThirdParty/hime b/Telegram/ThirdParty/hime index 752564559..62424e7ba 160000 --- a/Telegram/ThirdParty/hime +++ b/Telegram/ThirdParty/hime @@ -1 +1 @@ -Subproject commit 7525645598649afeab5bfe7fda612c2ad744e7a3 +Subproject commit 62424e7babdc4aa4bce9514064e47c6e4a2cf33e diff --git a/Telegram/lib_base b/Telegram/lib_base index 37f38c039..95b23bd09 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 37f38c039f69d80762d663b6c7e83deff1f87935 +Subproject commit 95b23bd09b71c5599b6b89d6c708e7d35b8963aa diff --git a/cmake b/cmake index 5318ac7c5..294a18baf 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 5318ac7c54dbf33b17ccc4bd3281bb8b8db25e02 +Subproject commit 294a18baf8896609aeb78b0d9b92a8011b0fa2e5 From fbe4e3f0ecadb801945f477cc44d6dff80acf092 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 17 May 2021 13:12:22 +0400 Subject: [PATCH 059/477] Fix build with mallocng. --- Telegram/SourceFiles/_other/packer.cpp | 2 +- .../platform/linux/specific_linux.cpp | 22 +++++++++++-------- Telegram/ThirdParty/hime | 2 +- cmake | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Telegram/SourceFiles/_other/packer.cpp b/Telegram/SourceFiles/_other/packer.cpp index 630c0b940..2fa976b3d 100644 --- a/Telegram/SourceFiles/_other/packer.cpp +++ b/Telegram/SourceFiles/_other/packer.cpp @@ -538,7 +538,7 @@ QString countAlphaVersionSignature(quint64 version) { // duplicated in autoupdat uint32 siglen = 0; - RSA *prKey = [] { + RSA *prKey = [&] { const auto bio = makeBIO( const_cast(cAlphaPrivateKey.constData()), -1); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6bc833ac0..da5a8f483 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -746,15 +746,19 @@ int psFixPrevious() { namespace Platform { void start() { - // avoid stripping custom allocator - malloc(0); - calloc(0, 0); - realloc(nullptr, 0); - free(nullptr); - aligned_alloc(0, 0); - malloc_usable_size(nullptr); - memalign(0, 0); - posix_memalign(nullptr, 0, 0); + // Avoid stripping custom allocator methods. + const auto address = [](auto method) { + return reinterpret_cast(method); + }; + const auto value = address(malloc) + + address(calloc) + + address(realloc) + + address(free) + + address(aligned_alloc) + + address(malloc_usable_size) + + address(memalign) + + address(posix_memalign); + DEBUG_LOG(("Malloc addresses %1.").arg(value ? "non-null" : "null")); LOG(("Launcher filename: %1").arg(QGuiApplication::desktopFileName())); diff --git a/Telegram/ThirdParty/hime b/Telegram/ThirdParty/hime index 62424e7ba..752564559 160000 --- a/Telegram/ThirdParty/hime +++ b/Telegram/ThirdParty/hime @@ -1 +1 @@ -Subproject commit 62424e7babdc4aa4bce9514064e47c6e4a2cf33e +Subproject commit 7525645598649afeab5bfe7fda612c2ad744e7a3 diff --git a/cmake b/cmake index 294a18baf..de6551913 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 294a18baf8896609aeb78b0d9b92a8011b0fa2e5 +Subproject commit de65519132126db2c2166cdf5af5f12e61652419 From e0d6faf45b5973b9a52fac33da10432149259186 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Wed, 19 May 2021 13:24:15 +0400 Subject: [PATCH 060/477] Fix build with LTO --- .../SourceFiles/platform/linux/specific_linux.cpp | 15 --------------- Telegram/ThirdParty/mallocng | 2 +- cmake | 2 +- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index da5a8f483..498b37372 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -59,7 +59,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include #include @@ -746,20 +745,6 @@ int psFixPrevious() { namespace Platform { void start() { - // Avoid stripping custom allocator methods. - const auto address = [](auto method) { - return reinterpret_cast(method); - }; - const auto value = address(malloc) - + address(calloc) - + address(realloc) - + address(free) - + address(aligned_alloc) - + address(malloc_usable_size) - + address(memalign) - + address(posix_memalign); - DEBUG_LOG(("Malloc addresses %1.").arg(value ? "non-null" : "null")); - LOG(("Launcher filename: %1").arg(QGuiApplication::desktopFileName())); qputenv("PULSE_PROP_application.name", AppName.utf8()); diff --git a/Telegram/ThirdParty/mallocng b/Telegram/ThirdParty/mallocng index 5db0b72cd..cf3eabc44 160000 --- a/Telegram/ThirdParty/mallocng +++ b/Telegram/ThirdParty/mallocng @@ -1 +1 @@ -Subproject commit 5db0b72cd85b20aec790fccdd31c17111afc7c94 +Subproject commit cf3eabc443292aad8ee63491274d7865b5e0864c diff --git a/cmake b/cmake index de6551913..bcc0ac15f 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit de65519132126db2c2166cdf5af5f12e61652419 +Subproject commit bcc0ac15f497513455aa144ffe4e658f7aca0e73 From 93e4161d5e3efd46d70f6e7e84a98a152140fdfd Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 18 May 2021 18:03:38 +0300 Subject: [PATCH 061/477] Fixed NuGet link in instructions for Windows build. --- docs/building-msvc-x64.md | 2 +- docs/building-msvc.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building-msvc-x64.md b/docs/building-msvc-x64.md index f5e73e1a6..92e91bdb1 100644 --- a/docs/building-msvc-x64.md +++ b/docs/building-msvc-x64.md @@ -27,7 +27,7 @@ You will require **api_id** and **api_hash** to access the Telegram API servers. * Download **CMake** installer from [https://cmake.org/download/](https://cmake.org/download/) and install to ***BuildPath*\\ThirdParty\\cmake** * Download **Ninja** executable from [https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip](https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip) and unpack to ***BuildPath*\\ThirdParty\\Ninja** * Download **Git** installer from [https://git-scm.com/download/win](https://git-scm.com/download/win) and install it. -* Download **NuGet** executable from [https://dist.nuget.org/win-x86-commandline/latest/nuget.exe](https://www.nuget.org/downloads) and put to ***BuildPath*\\ThirdParty\\NuGet** +* Download **NuGet** executable from [https://dist.nuget.org/win-x86-commandline/latest/nuget.exe](https://dist.nuget.org/win-x86-commandline/latest/nuget.exe) and put to ***BuildPath*\\ThirdParty\\NuGet** Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** and run diff --git a/docs/building-msvc.md b/docs/building-msvc.md index 28f10c181..8aaa69a47 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -27,7 +27,7 @@ You will require **api_id** and **api_hash** to access the Telegram API servers. * Download **CMake** installer from [https://cmake.org/download/](https://cmake.org/download/) and install to ***BuildPath*\\ThirdParty\\cmake** * Download **Ninja** executable from [https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip](https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-win.zip) and unpack to ***BuildPath*\\ThirdParty\\Ninja** * Download **Git** installer from [https://git-scm.com/download/win](https://git-scm.com/download/win) and install it. -* Download **NuGet** executable from [https://dist.nuget.org/win-x86-commandline/latest/nuget.exe](https://www.nuget.org/downloads) and put to ***BuildPath*\\ThirdParty\\NuGet** +* Download **NuGet** executable from [https://dist.nuget.org/win-x86-commandline/latest/nuget.exe](https://dist.nuget.org/win-x86-commandline/latest/nuget.exe) and put to ***BuildPath*\\ThirdParty\\NuGet** Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** and run From 5010c9033b06e96d1b81e8db4fef198bcb61efa2 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 22 May 2021 17:42:19 +0400 Subject: [PATCH 062/477] Specify buildtype for meson --- Telegram/build/docker/centos_env/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index bb23c5899..5fc8fdf11 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -288,6 +288,7 @@ WORKDIR libvdpau RUN sed -i 's/shared_library/library/g' src/meson.build RUN meson build \ + --buildtype=release \ --default-library=both \ --sysconfdir=/etc \ -Ddocumentation=false \ @@ -462,6 +463,7 @@ RUN git clone -b xkbcommon-1.0.3 --depth=1 $GIT/xkbcommon/libxkbcommon.git WORKDIR libxkbcommon RUN meson build \ + --buildtype=release \ --default-library=both \ -Denable-docs=false \ -Denable-wayland=false \ From a41b7b62ace869187aa40b0235cd78613543b607 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 15 Apr 2021 17:55:13 +0400 Subject: [PATCH 063/477] Build and link rnnoise on Windows. --- Telegram/ThirdParty/tgcalls | 2 +- Telegram/cmake/lib_tgcalls.cmake | 1 + docs/building-msvc.md | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 4a9de89c6..cdd2803ad 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 4a9de89c61882f00e0563d44516284a89874144b +Subproject commit cdd2803ad40cbba455f64ee93bc434698afc2e06 diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 1bda423a4..f417f963d 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -126,6 +126,7 @@ target_link_libraries(lib_tgcalls PRIVATE desktop-app::external_webrtc desktop-app::external_ffmpeg + desktop-app::external_rnnoise ) target_compile_definitions(lib_tgcalls diff --git a/docs/building-msvc.md b/docs/building-msvc.md index 8aaa69a47..e1739f366 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -146,6 +146,16 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** msbuild opus.sln /property:Configuration=Debug /property:Platform="Win32" msbuild opus.sln /property:Configuration=Release /property:Platform="Win32" + git clone https://github.com/desktop-app/rnnoise.git + cd rnnoise + git checkout tdesktop + mkdir out + cd out + cmake -A Win32 .. + cmake --build . --config Debug + cmake --build . --config Release + cd .. + cd ..\..\..\.. SET PATH_BACKUP_=%PATH% SET PATH=%cd%\ThirdParty\msys64\usr\bin;%PATH% From a6f379a17a6a2078388832c81670fc66903617b5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 15 Apr 2021 17:55:32 +0400 Subject: [PATCH 064/477] Parse and serialize video parameters. --- Telegram/SourceFiles/calls/calls_call.cpp | 2 +- Telegram/SourceFiles/calls/calls_call.h | 2 +- .../SourceFiles/calls/calls_group_call.cpp | 208 +++++++++++++++++- Telegram/SourceFiles/calls/calls_group_call.h | 12 + Telegram/SourceFiles/calls/calls_instance.cpp | 8 + Telegram/SourceFiles/calls/calls_instance.h | 7 +- Telegram/SourceFiles/data/data_group_call.cpp | 15 +- Telegram/SourceFiles/data/data_group_call.h | 6 + 8 files changed, 251 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index cbf41b004..8f80c38b1 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -380,7 +380,7 @@ void Call::setupOutgoingVideo() { // Paused not supported right now. Assert(state == Webrtc::VideoState::Active); if (!_videoCapture) { - _videoCapture = _delegate->getVideoCapture(); + _videoCapture = _delegate->callGetVideoCapture(); _videoCapture->setOutput(_videoOutgoing->sink()); } if (_instance) { diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index b84f5c51f..c61015a8d 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -72,7 +72,7 @@ public: Fn onSuccess, bool video) = 0; - virtual auto getVideoCapture() + virtual auto callGetVideoCapture() -> std::shared_ptr = 0; virtual ~Delegate() = default; diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 8edb10426..e730af122 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -27,12 +27,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "base/global_shortcuts.h" #include "base/openssl_help.h" +#include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_media_devices.h" #include "webrtc/webrtc_create_adm.h" #include +#include #include - +#include #include #include #include @@ -76,6 +78,11 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); } // namespace +struct VideoParams { + tgcalls::GroupParticipantDescription description; + uint32 hash = 0; +}; + class GroupCall::LoadPartTask final : public tgcalls::BroadcastPartTask { public: LoadPartTask( @@ -129,6 +136,114 @@ private: return false; } +std::shared_ptr ParseVideoParams( + const QByteArray &json, + const std::shared_ptr &existing) { + using namespace tgcalls; + + if (json.isEmpty()) { + return nullptr; + } + const auto hash = XXH32(json.data(), json.size(), uint32(0)); + if (existing && existing->hash == hash) { + return existing; + } + const auto data = existing ? existing : std::make_shared(); + data->hash = hash; + + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson(json, &error); + if (error.error != QJsonParseError::NoError) { + LOG(("API Error: " + "Failed to parse group call video params, error: %1." + ).arg(error.errorString())); + return data; + } else if (!document.isObject()) { + LOG(("API Error: " + "Not an object received in group call video params.")); + return data; + } + + const auto readString = []( + const QJsonObject &object, + const char *key) { + return object.value(key).toString().toStdString(); + }; + const auto object = document.object(); + data->description.endpointId = readString(object, "endpoint"); + + const auto ssrcGroups = object.value("ssrc-groups").toArray(); + data->description.videoSourceGroups.reserve(ssrcGroups.size()); + for (const auto &value : ssrcGroups) { + const auto inner = value.toObject(); + auto sources = std::vector(); + { + const auto list = inner.value("sources").toArray(); + sources.reserve(list.size()); + for (const auto &source : list) { + sources.push_back(uint32_t(source.toDouble())); + } + } + data->description.videoSourceGroups.push_back({ + .ssrcs = std::move(sources), + .semantics = readString(inner, "semantics"), + }); + } + + const auto payloadTypes = object.value("payload-types").toArray(); + data->description.videoPayloadTypes.reserve(payloadTypes.size()); + for (const auto &value : payloadTypes) { + const auto inner = value.toObject(); + + auto types = std::vector(); + { + const auto list = inner.value("rtcp-fbs").toArray(); + types.reserve(list.size()); + for (const auto &type : list) { + const auto inside = type.toObject(); + types.push_back({ + .type = readString(inside, "type"), + .subtype = readString(inside, "subtype"), + }); + } + } + auto parameters = std::vector>(); + { + const auto list = inner.value("parameters").toArray(); + parameters.reserve(list.size()); + for (const auto ¶meter : list) { + const auto inside = parameter.toObject(); + for (auto i = inside.begin(); i != inside.end(); ++i) { + parameters.push_back({ + i.key().toStdString(), + i.value().toString().toStdString(), + }); + } + } + } + data->description.videoPayloadTypes.push_back({ + .id = uint32_t(inner.value("id").toDouble()), + .name = readString(inner, "name"), + .clockrate = uint32_t(inner.value("clockrate").toDouble()), + .channels = uint32_t(inner.value("channels").toDouble()), + .feedbackTypes = std::move(types), + .parameters = std::move(parameters), + }); + } + + const auto extensionMap = object.value("rtp-hdrexts").toArray(); + data->description.videoExtensionMap.reserve(extensionMap.size()); + for (const auto &extension : extensionMap) { + const auto inner = extension.toObject(); + data->description.videoExtensionMap.push_back({ + uint32_t(inner.value("id").toDouble()), + readString(inner, "uri"), + }); + } + + return data; +} + GroupCall::LoadPartTask::LoadPartTask( base::weak_ptr call, int64 time, @@ -186,6 +301,8 @@ GroupCall::GroupCall( , _joinHash(info.joinHash) , _id(inputCall.c_inputGroupCall().vid().v) , _scheduleDate(info.scheduleDate) +, _videoOutgoing(std::make_unique( + Webrtc::VideoState::Inactive)) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) , _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) @@ -429,6 +546,13 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { const auto volumeChanged = was ? (was->volume != now.volume || was->mutedByMe != now.mutedByMe) : (now.volume != Group::kDefaultVolume || now.mutedByMe); + if (now.videoParams) { + auto participants = std::vector(); + participants.push_back(now.videoParams->description); + participants.back().audioSsrc = now.ssrc; + _instance->addParticipants(std::move(participants)); + } + if (volumeChanged) { _instance->setVolume( now.ssrc, @@ -511,12 +635,80 @@ void GroupCall::rejoin(not_null as) { fingerprints.push_back(object); } + auto extensionMap = QJsonArray(); + for (const auto &extension : payload.videoExtensionMap) { + auto object = QJsonObject(); + object.insert("id", int64(extension.first)); + object.insert( + "uri", + QString::fromStdString(extension.second)); + extensionMap.push_back(object); + } + + auto payloadTypes = QJsonArray(); + for (const auto &type : payload.videoPayloadTypes) { + auto object = QJsonObject(); + object.insert("id", int64(type.id)); + object.insert("name", QString::fromStdString(type.name)); + object.insert("clockrate", int64(type.clockrate)); + if (!type.parameters.empty()) { + auto parameters = QJsonObject(); + for (const auto ¶meter : type.parameters) { + parameters.insert( + QString::fromStdString(parameter.first), + QString::fromStdString(parameter.second)); + } + object.insert("parameters", parameters); + } + if (type.name != "rtx") { + object.insert("channels", int64(type.channels)); + auto fbs = QJsonArray(); + for (const auto &element : type.feedbackTypes) { + auto inner = QJsonObject(); + inner.insert( + "type", + QString::fromStdString(element.type)); + if (!element.subtype.empty()) { + inner.insert( + "subtype", + QString::fromStdString(element.subtype)); + } + fbs.push_back(inner); + } + object.insert("rtcp-fbs", fbs); + } + payloadTypes.push_back(object); + } + + auto sourceGroups = QJsonArray(); + for (const auto &group : payload.videoSourceGroups) { + auto object = QJsonObject(); + object.insert( + "semantics", + QString::fromStdString(group.semantics)); + auto list = QJsonArray(); + for (const auto source : group.ssrcs) { + list.push_back(int64(source)); + } + object.insert("sources", list); + sourceGroups.push_back(object); + } + auto root = QJsonObject(); const auto ssrc = payload.ssrc; root.insert("ufrag", QString::fromStdString(payload.ufrag)); root.insert("pwd", QString::fromStdString(payload.pwd)); root.insert("fingerprints", fingerprints); root.insert("ssrc", double(payload.ssrc)); + if (!extensionMap.isEmpty()) { + root.insert("rtp-hdrexts", extensionMap); + } + if (!payloadTypes.isEmpty()) { + root.insert("payload-types", payloadTypes); + } + if (!sourceGroups.isEmpty()) { + root.insert("ssrc-groups", sourceGroups); + } LOG(("Call Info: Join payload received, joining with ssrc: %1." ).arg(ssrc)); @@ -895,7 +1087,7 @@ void GroupCall::handlePossibleCreateOrJoinResponse( const auto candidates = root.value("candidates").toArray(); for (const auto &print : prints) { const auto object = print.toObject(); - payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{ + payload.fingerprints.push_back({ .hash = readString(object, "hash"), .setup = readString(object, "setup"), .fingerprint = readString(object, "fingerprint"), @@ -903,7 +1095,7 @@ void GroupCall::handlePossibleCreateOrJoinResponse( } for (const auto &candidate : candidates) { const auto object = candidate.toObject(); - payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{ + payload.candidates.push_back({ .port = readString(object, "port"), .protocol = readString(object, "protocol"), .network = readString(object, "network"), @@ -945,7 +1137,9 @@ void GroupCall::addParticipantsToInstance() { void GroupCall::prepareParticipantForAdding( const Data::GroupCallParticipant &participant) { - _preparedParticipants.push_back(tgcalls::GroupParticipantDescription()); + _preparedParticipants.push_back(participant.videoParams + ? participant.videoParams->description + : tgcalls::GroupParticipantDescription()); auto &added = _preparedParticipants.back(); added.audioSsrc = participant.ssrc; _unresolvedSsrcs.remove(added.audioSsrc); @@ -1142,6 +1336,11 @@ void GroupCall::ensureControllerCreated() { } const auto &settings = Core::App().settings(); + if (!_videoCapture) { + _videoCapture = _delegate->groupCallGetVideoCapture(); + _videoCapture->setOutput(_videoOutgoing->sink()); + } + const auto weak = base::make_weak(this); const auto myLevel = std::make_shared(); tgcalls::GroupInstanceDescriptor descriptor = { @@ -1170,6 +1369,7 @@ void GroupCall::ensureControllerCreated() { .initialOutputDeviceId = _audioOutputId.toStdString(), .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( settings.callAudioBackend()), + .videoCapture = _videoCapture, .participantDescriptionsRequired = [=]( const std::vector &ssrcs) { crl::on_main(weak, [=] { diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index 90f0f8f9f..f2b7972aa 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -20,6 +20,7 @@ class GroupInstanceCustomImpl; struct GroupLevelsUpdate; struct GroupNetworkState; struct GroupParticipantDescription; +class VideoCaptureInterface; } // namespace tgcalls namespace base { @@ -29,6 +30,7 @@ class GlobalShortcutValue; namespace Webrtc { class MediaDevices; +class VideoTrack; } // namespace Webrtc namespace Data { @@ -72,6 +74,12 @@ struct LevelUpdate { bool me = false; }; +struct VideoParams; + +[[nodiscard]] std::shared_ptr ParseVideoParams( + const QByteArray &json, + const std::shared_ptr &existing); + class GroupCall final : public base::has_weak_ptr { public: class Delegate { @@ -90,6 +98,8 @@ public: Ended, }; virtual void groupCallPlaySound(GroupCallSound sound) = 0; + virtual auto groupCallGetVideoCapture() + -> std::shared_ptr = 0; }; using GlobalShortcutManager = base::GlobalShortcutManager; @@ -336,6 +346,8 @@ private: mtpRequestId _updateMuteRequestId = 0; std::unique_ptr _instance; + std::shared_ptr _videoCapture; + const std::unique_ptr _videoOutgoing; rpl::event_stream _levelUpdates; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index af869bd63..b59a9f42b 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -592,6 +592,14 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn } } +std::shared_ptr Instance::callGetVideoCapture() { + return getVideoCapture(); +} + +std::shared_ptr Instance::groupCallGetVideoCapture() { + return getVideoCapture(); +} + std::shared_ptr Instance::getVideoCapture() { if (auto result = _videoCapture.lock()) { return result; diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 96034fcaf..c7e66a47b 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -69,8 +69,7 @@ public: bool activateCurrentCall(const QString &joinHash = QString()); bool minimizeCurrentActiveCall(); bool closeCurrentActiveCall(); - auto getVideoCapture() - -> std::shared_ptr override; + std::shared_ptr getVideoCapture(); void requestPermissionsOrFail(Fn onSuccess, bool video = true); void setCurrentAudioDevice(bool input, const QString &deviceId); @@ -103,6 +102,8 @@ private: requestPermissionsOrFail(std::move(onSuccess), video); } void callPlaySound(CallSound sound) override; + auto callGetVideoCapture() + ->std::shared_ptr override; void groupCallFinished(not_null call) override; void groupCallFailed(not_null call) override; @@ -110,6 +111,8 @@ private: requestPermissionsOrFail(std::move(onSuccess), false); } void groupCallPlaySound(GroupCallSound sound) override; + auto groupCallGetVideoCapture() + ->std::shared_ptr override; void createCall(not_null user, Call::Type type, bool video); void destroyCall(not_null call); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 1ec22213c..c590b54f7 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -343,7 +343,7 @@ void GroupCall::applyLocalUpdate( const MTPDupdateGroupCallParticipants &update) { applyParticipantsSlice( update.vparticipants().v, - ApplySliceSource::UpdateReceived); + ApplySliceSource::UpdateConstructed); } void GroupCall::applyEnqueuedUpdate(const MTPUpdate &update) { @@ -529,8 +529,21 @@ void GroupCall::applyParticipantsSlice( && (!was || was->onlyMinLoaded); const auto raisedHandRating = data.vraise_hand_rating().value_or_empty(); + const auto hasVideoParamsInformation = (sliceSource + != ApplySliceSource::UpdateConstructed); const auto value = Participant{ .peer = participantPeer, + .videoParams = (hasVideoParamsInformation + ? Calls::ParseVideoParams( + (data.vparams() + ? data.vparams()->c_dataJSON().vdata().v + : QByteArray()), + (i != end(_participants) + ? i->videoParams + : nullptr)) + : (i != end(_participants)) + ? i->videoParams + : nullptr), .date = data.vdate().v, .lastActive = lastActive, .raisedHandRating = raisedHandRating, diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 078f5634e..c2dd507d2 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -13,6 +13,10 @@ class PeerData; class ApiWrap; +namespace Calls { +struct VideoParams; +} // namespace Calls + namespace Data { struct LastSpokeTimes { @@ -22,6 +26,7 @@ struct LastSpokeTimes { struct GroupCallParticipant { not_null peer; + std::shared_ptr videoParams; TimeId date = 0; TimeId lastActive = 0; uint64 raisedHandRating = 0; @@ -131,6 +136,7 @@ private: SliceLoaded, UnknownLoaded, UpdateReceived, + UpdateConstructed, }; enum class QueuedType : uint8 { VersionedParticipant, From ba02a5c46a7780897fd1e8b4166e6fd0c2539ebf Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 15 Apr 2021 18:21:27 +0400 Subject: [PATCH 065/477] Resolve video chat participants by unknown ssrcs. --- .../SourceFiles/calls/calls_group_call.cpp | 63 +++++++++++++------ Telegram/SourceFiles/calls/calls_group_call.h | 2 + Telegram/SourceFiles/data/data_group_call.cpp | 55 +++++++++++++--- Telegram/SourceFiles/data/data_group_call.h | 6 +- 4 files changed, 96 insertions(+), 30 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index e730af122..32b2da0d0 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -80,6 +80,7 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); struct VideoParams { tgcalls::GroupParticipantDescription description; + base::flat_set videoSsrcs; uint32 hash = 0; }; @@ -148,7 +149,11 @@ std::shared_ptr ParseVideoParams( if (existing && existing->hash == hash) { return existing; } - const auto data = existing ? existing : std::make_shared(); + // We don't reuse existing pointer, that way we can compare pointers + // to see if anything was changed in video params. + const auto data = /*existing + ? existing + : */std::make_shared(); data->hash = hash; auto error = QJsonParseError{ 0, QJsonParseError::NoError }; @@ -181,7 +186,9 @@ std::shared_ptr ParseVideoParams( const auto list = inner.value("sources").toArray(); sources.reserve(list.size()); for (const auto &source : list) { - sources.push_back(uint32_t(source.toDouble())); + const auto ssrc = uint32_t(source.toDouble()); + sources.push_back(ssrc); + data->videoSsrcs.emplace(ssrc); } } data->description.videoSourceGroups.push_back({ @@ -209,16 +216,13 @@ std::shared_ptr ParseVideoParams( } auto parameters = std::vector>(); { - const auto list = inner.value("parameters").toArray(); + const auto list = inner.value("parameters").toObject(); parameters.reserve(list.size()); - for (const auto ¶meter : list) { - const auto inside = parameter.toObject(); - for (auto i = inside.begin(); i != inside.end(); ++i) { - parameters.push_back({ - i.key().toStdString(), - i.value().toString().toStdString(), - }); - } + for (auto i = list.begin(); i != list.end(); ++i) { + parameters.push_back({ + i.key().toStdString(), + i.value().toString().toStdString(), + }); } } data->description.videoPayloadTypes.push_back({ @@ -244,6 +248,14 @@ std::shared_ptr ParseVideoParams( return data; } +const base::flat_set &VideoSourcesFromParams( + const std::shared_ptr ¶ms) { + static const auto kEmpty = base::flat_set(); + return (params && !params->videoSsrcs.empty()) + ? params->videoSsrcs + : kEmpty; +} + GroupCall::LoadPartTask::LoadPartTask( base::weak_ptr call, int64 time, @@ -544,13 +556,18 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { const auto &now = *update.now; const auto &was = update.was; const auto volumeChanged = was - ? (was->volume != now.volume || was->mutedByMe != now.mutedByMe) + ? (was->volume != now.volume + || was->mutedByMe != now.mutedByMe) : (now.volume != Group::kDefaultVolume || now.mutedByMe); - if (now.videoParams) { - auto participants = std::vector(); - participants.push_back(now.videoParams->description); - participants.back().audioSsrc = now.ssrc; - _instance->addParticipants(std::move(participants)); + if (now.videoParams + && now.ssrc + && (!was + || was->videoParams != now.videoParams + || was->ssrc != now.ssrc) + && (now.peer != _joinAs) + && (_instanceMode != InstanceMode::None)) { + prepareParticipantForAdding(now); + addPreparedParticipantsDelayed(); } if (volumeChanged) { @@ -1113,6 +1130,8 @@ void GroupCall::handlePossibleCreateOrJoinResponse( } setInstanceMode(InstanceMode::Rtc); _instance->setJoinResponsePayload(payload, {}); + + addParticipantsToInstance(); }); } @@ -1143,6 +1162,11 @@ void GroupCall::prepareParticipantForAdding( auto &added = _preparedParticipants.back(); added.audioSsrc = participant.ssrc; _unresolvedSsrcs.remove(added.audioSsrc); + for (const auto &group : added.videoSourceGroups) { + for (const auto ssrc : group.ssrcs) { + _unresolvedSsrcs.remove(ssrc); + } + } } void GroupCall::addPreparedParticipants() { @@ -1499,7 +1523,10 @@ void GroupCall::requestParticipantsInformation( const auto &existing = real->participants(); for (const auto ssrc : ssrcs) { - const auto participantPeer = real->participantPeerBySsrc(ssrc); + const auto byAudio = real->participantPeerByAudioSsrc(ssrc); + const auto participantPeer = byAudio + ? byAudio + : real->participantPeerByVideoSsrc(ssrc); if (!participantPeer) { _unresolvedSsrcs.emplace(ssrc); continue; diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index f2b7972aa..917ca84b7 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -79,6 +79,8 @@ struct VideoParams; [[nodiscard]] std::shared_ptr ParseVideoParams( const QByteArray &json, const std::shared_ptr &existing); +[[nodiscard]] const base::flat_set &VideoSourcesFromParams( + const std::shared_ptr ¶ms); class GroupCall final : public base::has_weak_ptr { public: diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index c590b54f7..061a7fe7b 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -186,9 +186,18 @@ bool GroupCall::participantsLoaded() const { return _allParticipantsLoaded; } -PeerData *GroupCall::participantPeerBySsrc(uint32 ssrc) const { - const auto i = _participantPeerBySsrc.find(ssrc); - return (i != end(_participantPeerBySsrc)) ? i->second.get() : nullptr; +PeerData *GroupCall::participantPeerByAudioSsrc(uint32 ssrc) const { + const auto i = _participantPeerByAudioSsrc.find(ssrc); + return (i != end(_participantPeerByAudioSsrc)) + ? i->second.get() + : nullptr; +} + +PeerData *GroupCall::participantPeerByVideoSsrc(uint32 ssrc) const { + const auto i = _participantPeerByVideoSsrc.find(ssrc); + return (i != end(_participantPeerByVideoSsrc)) + ? i->second.get() + : nullptr; } rpl::producer<> GroupCall::participantsSliceAdded() { @@ -295,7 +304,8 @@ void GroupCall::processFullCallFields(const MTPphone_GroupCall &call) { data.vcall().match([&](const MTPDgroupCall &data) { _participants.clear(); _speakingByActiveFinishes.clear(); - _participantPeerBySsrc.clear(); + _participantPeerByAudioSsrc.clear(); + _participantPeerByVideoSsrc.clear(); _allParticipantsLoaded = false; applyParticipantsSlice( @@ -488,7 +498,11 @@ void GroupCall::applyParticipantsSlice( auto update = ParticipantUpdate{ .was = *i, }; - _participantPeerBySsrc.erase(i->ssrc); + _participantPeerByAudioSsrc.erase(i->ssrc); + const auto &all = VideoSourcesFromParams(i->videoParams); + for (const auto ssrc : all) { + _participantPeerByVideoSsrc.erase(ssrc); + } _speakingByActiveFinishes.remove(participantPeer); _participants.erase(i); if (sliceSource != ApplySliceSource::SliceLoaded) { @@ -557,18 +571,39 @@ void GroupCall::applyParticipantsSlice( .onlyMinLoaded = onlyMinLoaded, }; if (i == end(_participants)) { - _participantPeerBySsrc.emplace(value.ssrc, participantPeer); + _participantPeerByAudioSsrc.emplace( + value.ssrc, + participantPeer); + const auto &all = VideoSourcesFromParams(value.videoParams); + for (const auto ssrc : all) { + _participantPeerByVideoSsrc.emplace( + ssrc, + participantPeer); + } _participants.push_back(value); if (const auto user = participantPeer->asUser()) { _peer->owner().unregisterInvitedToCallUser(_id, user); } } else { if (i->ssrc != value.ssrc) { - _participantPeerBySsrc.erase(i->ssrc); - _participantPeerBySsrc.emplace( + _participantPeerByAudioSsrc.erase(i->ssrc); + _participantPeerByAudioSsrc.emplace( value.ssrc, participantPeer); } + if (i->videoParams != value.videoParams) { + const auto &old = VideoSourcesFromParams(i->videoParams); + for (const auto ssrc : old) { + _participantPeerByVideoSsrc.erase(ssrc); + } + const auto &now = VideoSourcesFromParams( + value.videoParams); + for (const auto ssrc : now) { + _participantPeerByVideoSsrc.emplace( + ssrc, + participantPeer); + } + } *i = value; } if (data.is_just_joined()) { @@ -592,8 +627,8 @@ void GroupCall::applyLastSpoke( uint32 ssrc, LastSpokeTimes when, crl::time now) { - const auto i = _participantPeerBySsrc.find(ssrc); - if (i == end(_participantPeerBySsrc)) { + const auto i = _participantPeerByAudioSsrc.find(ssrc); + if (i == end(_participantPeerByAudioSsrc)) { _unknownSpokenSsrcs[ssrc] = when; requestUnknownParticipants(); return; diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index c2dd507d2..747b43b50 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -102,7 +102,8 @@ public: -> const std::vector &; void requestParticipants(); [[nodiscard]] bool participantsLoaded() const; - [[nodiscard]] PeerData *participantPeerBySsrc(uint32 ssrc) const; + [[nodiscard]] PeerData *participantPeerByAudioSsrc(uint32 ssrc) const; + [[nodiscard]] PeerData *participantPeerByVideoSsrc(uint32 ssrc) const; [[nodiscard]] rpl::producer<> participantsSliceAdded(); [[nodiscard]] rpl::producer participantUpdated() const; @@ -181,7 +182,8 @@ private: std::optional _savedFull; std::vector _participants; - base::flat_map> _participantPeerBySsrc; + base::flat_map> _participantPeerByAudioSsrc; + base::flat_map> _participantPeerByVideoSsrc; base::flat_map, crl::time> _speakingByActiveFinishes; base::Timer _speakingByActiveFinishTimer; QString _nextOffset; From ebdbe4a8d63ab832c08f603d3ec0df55e422e28b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 15 Apr 2021 18:37:12 +0400 Subject: [PATCH 066/477] Show video instead of userpics in members list. --- .../SourceFiles/calls/calls_group_call.cpp | 31 ++++++++- Telegram/SourceFiles/calls/calls_group_call.h | 8 +++ .../SourceFiles/calls/calls_group_members.cpp | 69 +++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 32b2da0d0..ece53bff9 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -991,6 +991,18 @@ void GroupCall::toggleScheduleStartSubscribed(bool subscribed) { }).send(); } +void GroupCall::addVideoOutput( + uint32 ssrc, + not_null track) { + if (_instance) { + _instance->addIncomingVideoOutput(ssrc, track->sink()); + } +} + +not_null GroupCall::outgoingVideoTrack() const { + return _videoOutgoing.get(); +} + void GroupCall::setMuted(MuteState mute) { const auto set = [=] { const auto wasMuted = (muted() == MuteState::Muted) @@ -1362,6 +1374,7 @@ void GroupCall::ensureControllerCreated() { if (!_videoCapture) { _videoCapture = _delegate->groupCallGetVideoCapture(); + _videoOutgoing->setState(Webrtc::VideoState::Active); _videoCapture->setOutput(_videoOutgoing->sink()); } @@ -1394,6 +1407,15 @@ void GroupCall::ensureControllerCreated() { .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( settings.callAudioBackend()), .videoCapture = _videoCapture, + //.getVideoSource = [=] { + // return _videoCapture-> + //}, + .incomingVideoSourcesUpdated = [=]( + const std::vector &ssrcs) { + crl::on_main(weak, [=] { + showVideoStreams(ssrcs); + }); + }, .participantDescriptionsRequired = [=]( const std::vector &ssrcs) { crl::on_main(weak, [=] { @@ -1413,7 +1435,8 @@ void GroupCall::ensureControllerCreated() { broadcastPartStart(std::move(result)); }); return result; - } + }, + .enableVideo = true, }; if (Logs::DebugEnabled()) { auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); @@ -1542,6 +1565,12 @@ void GroupCall::requestParticipantsInformation( addPreparedParticipants(); } +void GroupCall::showVideoStreams(const std::vector &ssrcs) { + for (const auto ssrc : ssrcs) { + _videoStreamUpdated.fire_copy(ssrc); + } +} + void GroupCall::updateInstanceMuteState() { Expects(_instance != nullptr); diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index 917ca84b7..c1173780f 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -146,6 +146,9 @@ public: void startScheduledNow(); void toggleScheduleStartSubscribed(bool subscribed); + void addVideoOutput(uint32 ssrc, not_null track); + [[nodiscard]] not_null outgoingVideoTrack() const; + void setMuted(MuteState mute); void setMutedAndUpdate(MuteState mute); [[nodiscard]] MuteState muted() const { @@ -191,6 +194,9 @@ public: [[nodiscard]] rpl::producer levelUpdates() const { return _levelUpdates.events(); } + [[nodiscard]] rpl::producer videoStreamUpdated() const { + return _videoStreamUpdated.events(); + } [[nodiscard]] rpl::producer rejoinEvents() const { return _rejoinEvents.events(); } @@ -294,6 +300,7 @@ private: const Data::GroupCallParticipant &participant); void addPreparedParticipants(); void addPreparedParticipantsDelayed(); + void showVideoStreams(const std::vector &ssrcs); void editParticipant( not_null participantPeer, @@ -351,6 +358,7 @@ private: std::shared_ptr _videoCapture; const std::unique_ptr _videoOutgoing; rpl::event_stream _levelUpdates; + rpl::event_stream _videoStreamUpdated; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; rpl::event_stream<> _allowedToSpeakNotifications; diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index 5ad91316f..1acdf4536 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "window/window_controller.h" // Controller::sessionController. #include "window/window_session_controller.h" +#include "webrtc/webrtc_video_track.h" #include "styles/style_calls.h" namespace Calls::Group { @@ -141,6 +142,9 @@ public: return _raisedHandRating; } + [[nodiscard]] not_null createVideoTrack(); + void setVideoTrack(not_null track); + void addActionRipple(QPoint point, Fn updateCallback) override; void stopLastActionRipple() override; @@ -244,6 +248,9 @@ private: std::unique_ptr _actionRipple; std::unique_ptr _blobsAnimation; std::unique_ptr _statusIcon; + std::unique_ptr _videoTrack; + Webrtc::VideoTrack *_videoTrackShown = nullptr; + rpl::lifetime _videoTrackLifetime; // #TODO calls move to unique_ptr. Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon. Ui::Animations::Simple _mutedAnimation; // For gray/red icon. Ui::Animations::Simple _activeAnimation; // For icon cross animation. @@ -635,6 +642,28 @@ void Row::ensureUserpicCache( auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback { auto userpic = ensureUserpicView(); return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { + const auto videoSize = _videoTrackShown + ? _videoTrackShown->frameSize() + : QSize(); + if (!videoSize.isEmpty()) { + const auto resize = (videoSize.width() > videoSize.height()) + ? QSize(videoSize.width() * size / videoSize.height(), size) + : QSize(size, videoSize.height() * size / videoSize.width()); + const auto request = Webrtc::FrameRequest{ + .resize = resize, + .outer = QSize(size, size), + }; + const auto frame = _videoTrackShown->frame(request); + auto copy = frame; // #TODO calls optimize. + copy.detach(); + Images::prepareCircle(copy); + p.drawImage(x, y, copy); + _videoTrackShown->markFrameShown(); + return; + } else if (_videoTrackShown) { + // We could skip the first notification. + _videoTrackShown->markFrameShown(); + } if (_blobsAnimation) { const auto mutedByMe = (_state == State::MutedByMe); const auto shift = QPointF(x + size / 2., y + size / 2.); @@ -876,6 +905,27 @@ void Row::refreshStatus() { _speaking); } +not_null Row::createVideoTrack() { + _videoTrackShown = nullptr; + _videoTrack = std::make_unique( + Webrtc::VideoState::Active); + setVideoTrack(_videoTrack.get()); + return _videoTrack.get(); +} + +void Row::setVideoTrack(not_null track) { + _videoTrackLifetime.destroy(); + _videoTrackShown = track; + _videoTrackShown->renderNextFrame( + ) | rpl::start_with_next([=] { + _delegate->rowUpdateRow(this); + if (_videoTrackShown->frameSize().isEmpty()) { + _videoTrackShown->markFrameShown(); + } + }, _videoTrackLifetime); + _delegate->rowUpdateRow(this); +} + void Row::addActionRipple(QPoint point, Fn updateCallback) { if (!_actionRipple) { auto mask = Ui::RippleAnimation::ellipseMask(QSize( @@ -980,6 +1030,20 @@ void MembersController::setupListChangeViewers() { } }, _lifetime); + _call->videoStreamUpdated( + ) | rpl::start_with_next([=](uint32 ssrc) { + const auto real = _call->lookupReal(); + const auto participantPeer = real + ? real->participantPeerByAudioSsrc(ssrc) + : nullptr; + const auto row = participantPeer + ? findRow(participantPeer) + : nullptr; + if (row) { + _call->addVideoOutput(ssrc, row->createVideoTrack()); + } + }, _lifetime); + _call->rejoinEvents( ) | rpl::start_with_next([=](const Group::RejoinEvent &event) { const auto guard = gsl::finally([&] { @@ -1266,6 +1330,11 @@ void MembersController::updateRow( Assert(nowSsrc != 0); _soundingRowBySsrc.emplace(nowSsrc, row); } + if (isMe(row->peer())) { + row->setVideoTrack(_call->outgoingVideoTrack()); + } else if (nowSsrc) { + _call->addVideoOutput(nowSsrc, row->createVideoTrack()); + } } const auto nowNoSounding = _soundingRowBySsrc.empty(); if (wasNoSounding && !nowNoSounding) { From 38cb1b195ddde799dd2cb44a347369dc75cbebaf Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 16 Apr 2021 18:15:56 +0400 Subject: [PATCH 067/477] Add proof-of-concept screen sharing on Windows. --- .../SourceFiles/calls/calls_group_call.cpp | 85 ++++++++++++++----- Telegram/SourceFiles/calls/calls_group_call.h | 9 ++ .../SourceFiles/calls/calls_group_panel.cpp | 7 +- Telegram/ThirdParty/tgcalls | 2 +- Telegram/cmake/lib_tgcalls.cmake | 32 +++++++ 5 files changed, 111 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index ece53bff9..fb47c903c 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -368,6 +368,9 @@ GroupCall::GroupCall( _realChanges.fire_copy(real); }, _lifetime); } + + setupMediaDevices(); + if (_id) { join(inputCall); } else { @@ -376,26 +379,31 @@ GroupCall::GroupCall( if (_scheduleDate) { saveDefaultJoinAs(_joinAs); } - - _mediaDevices->audioInputId( - ) | rpl::start_with_next([=](QString id) { - _audioInputId = id; - if (_instance) { - _instance->setAudioInputDevice(id.toStdString()); - } - }, _lifetime); - - _mediaDevices->audioOutputId( - ) | rpl::start_with_next([=](QString id) { - _audioOutputId = id; - if (_instance) { - _instance->setAudioOutputDevice(id.toStdString()); - } - }, _lifetime); } GroupCall::~GroupCall() { destroyController(); + switchToCamera(); +} + +bool GroupCall::isScreenSharing() const { + return (_videoDeviceId != _videoInputId); +} + +void GroupCall::switchToCamera() { + if (!_videoCapture || !isScreenSharing()) { + return; + } + _videoDeviceId = _videoInputId; + _videoCapture->switchToDevice(_videoDeviceId.toStdString()); +} + +void GroupCall::switchToScreenSharing() { + if (isScreenSharing()) { + return; + } + _videoDeviceId = "desktop_capturer_"; + _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } void GroupCall::setScheduledDate(TimeId date) { @@ -1322,6 +1330,41 @@ void GroupCall::applyOtherParticipantUpdate( }); } +void GroupCall::setupMediaDevices() { + _mediaDevices->audioInputId( + ) | rpl::start_with_next([=](QString id) { + _audioInputId = id; + if (_instance) { + _instance->setAudioInputDevice(id.toStdString()); + } + }, _lifetime); + + _mediaDevices->audioOutputId( + ) | rpl::start_with_next([=](QString id) { + _audioOutputId = id; + if (_instance) { + _instance->setAudioOutputDevice(id.toStdString()); + } + }, _lifetime); + + _mediaDevices->videoInputId( + ) | rpl::start_with_next([=](QString id) { + const auto usedCamera = !isScreenSharing(); + _videoInputId = id; + if (_videoCapture && usedCamera) { + _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + } + }, _lifetime); + setupOutgoingVideo(); +} + +void GroupCall::setupOutgoingVideo() { + _videoCapture = _delegate->groupCallGetVideoCapture(); + _videoOutgoing->setState(Webrtc::VideoState::Active); + _videoCapture->setOutput(_videoOutgoing->sink()); + _videoDeviceId = _videoInputId; +} + void GroupCall::changeTitle(const QString &title) { const auto real = lookupReal(); if (!real || real->title() == title) { @@ -1372,12 +1415,6 @@ void GroupCall::ensureControllerCreated() { } const auto &settings = Core::App().settings(); - if (!_videoCapture) { - _videoCapture = _delegate->groupCallGetVideoCapture(); - _videoOutgoing->setState(Webrtc::VideoState::Active); - _videoCapture->setOutput(_videoOutgoing->sink()); - } - const auto weak = base::make_weak(this); const auto myLevel = std::make_shared(); tgcalls::GroupInstanceDescriptor descriptor = { @@ -1821,6 +1858,10 @@ void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) { } } +void GroupCall::setCurrentVideoDevice(const QString &deviceId) { + _mediaDevices->switchToVideoInput(deviceId); +} + void GroupCall::toggleMute(const Group::MuteRequest &data) { if (data.locallyOnly) { applyParticipantLocally(data.peer, data.mute, std::nullopt); diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index c1173780f..e88f7c20d 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -209,6 +209,10 @@ public: static constexpr auto kSpeakLevelThreshold = 0.2; void setCurrentAudioDevice(bool input, const QString &deviceId); + void setCurrentVideoDevice(const QString &deviceId); + bool isScreenSharing() const; + void switchToCamera(); + void switchToScreenSharing(); //void setAudioVolume(bool input, float level); void setAudioDuckingEnabled(bool enabled); @@ -314,6 +318,9 @@ private: void applySelfUpdate(const MTPDgroupCallParticipant &data); void applyOtherParticipantUpdate(const MTPDgroupCallParticipant &data); + void setupMediaDevices(); + void setupOutgoingVideo(); + [[nodiscard]] MTPInputGroupCall inputCall() const; const not_null _delegate; @@ -377,6 +384,8 @@ private: std::unique_ptr _mediaDevices; QString _audioInputId; QString _audioOutputId; + QString _videoInputId; + QString _videoDeviceId; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index caa700953..7767c2029 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -673,7 +673,12 @@ void Panel::refreshLeftButton() { _share.destroy(); _settings.create(widget(), st::groupCallSettings); _settings->setClickedCallback([=] { - _layerBg->showBox(Box(SettingsBox, _call)); + if (_call->isScreenSharing()) { + _call->switchToCamera(); + } else { + _call->switchToScreenSharing(); + } + //_layerBg->showBox(Box(SettingsBox, _call)); }); _settings->setText(tr::lng_group_call_settings()); } diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index cdd2803ad..082b938cb 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit cdd2803ad40cbba455f64ee93bc434698afc2e06 +Subproject commit 082b938cbfef6539bdf6b71f9b46f9c2b2880d24 diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index f417f963d..0d870dade 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -56,6 +56,19 @@ PRIVATE VideoCaptureInterfaceImpl.h VideoCapturerInterface.h + # Desktop capturer + desktop_capturer/DesktopCaptureSource.h + desktop_capturer/DesktopCaptureSource.mm + desktop_capturer/DesktopCaptureSourceHelper.h + desktop_capturer/DesktopCaptureSourceHelper.mm + desktop_capturer/DesktopCaptureSourceManager.h + desktop_capturer/DesktopCaptureSourceManager.mm + desktop_capturer/DesktopCaptureSourceView.h + desktop_capturer/DesktopCaptureSourceView.mm + desktop_capturer/DesktopSharingCapturer.h + desktop_capturer/DesktopSharingCapturer.mm + + # Group calls group/GroupInstanceCustomImpl.cpp group/GroupInstanceCustomImpl.h group/GroupNetworkManager.cpp @@ -80,6 +93,8 @@ PRIVATE platform/darwin/DarwinInterface.mm platform/darwin/GLVideoView.h platform/darwin/GLVideoView.mm + platform/darwin/GLVideoViewMac.h + platform/darwin/GLVideoViewMac.mm platform/darwin/TGRTCCVPixelBuffer.h platform/darwin/TGRTCCVPixelBuffer.mm platform/darwin/TGRTCDefaultVideoDecoderFactory.h @@ -108,6 +123,8 @@ PRIVATE # POSIX # Teleram Desktop + platform/tdesktop/DesktopCapturer.cpp + platform/tdesktop/DesktopCapturer.h platform/tdesktop/DesktopInterface.cpp platform/tdesktop/DesktopInterface.h platform/tdesktop/VideoCapturerInterfaceImpl.cpp @@ -122,6 +139,19 @@ PRIVATE reference/InstanceImplReference.h ) +remove_target_sources(lib_tgcalls ${tgcalls_loc} + desktop_capturer/DesktopCaptureSource.h + desktop_capturer/DesktopCaptureSource.mm + desktop_capturer/DesktopCaptureSourceHelper.h + desktop_capturer/DesktopCaptureSourceHelper.mm + desktop_capturer/DesktopCaptureSourceManager.h + desktop_capturer/DesktopCaptureSourceManager.mm + desktop_capturer/DesktopCaptureSourceView.h + desktop_capturer/DesktopCaptureSourceView.mm + desktop_capturer/DesktopSharingCapturer.h + desktop_capturer/DesktopSharingCapturer.mm +) + target_link_libraries(lib_tgcalls PRIVATE desktop-app::external_webrtc @@ -152,6 +182,8 @@ elseif (APPLE) remove_target_sources(lib_tgcalls ${tgcalls_loc} platform/darwin/GLVideoView.h platform/darwin/GLVideoView.mm + platform/darwin/GLVideoViewMac.h + platform/darwin/GLVideoViewMac.mm platform/darwin/VideoCameraCapturer.h platform/darwin/VideoCameraCapturer.mm platform/darwin/VideoMetalView.h From fef1f80570030d5e69442a29be1250d3c3561792 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 16 Apr 2021 20:03:58 +0400 Subject: [PATCH 068/477] Disable video preview in Settings when in group call. --- Telegram/SourceFiles/settings/settings_calls.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/settings/settings_calls.cpp b/Telegram/SourceFiles/settings/settings_calls.cpp index 3a6b277c0..189f2bbc5 100644 --- a/Telegram/SourceFiles/settings/settings_calls.cpp +++ b/Telegram/SourceFiles/settings/settings_calls.cpp @@ -154,7 +154,9 @@ void Calls::setupContent() { track->renderNextFrame( ) | rpl::start_with_next([=] { const auto size = track->frameSize(); - if (size.isEmpty() || Core::App().calls().currentCall()) { + if (size.isEmpty() + || Core::App().calls().currentCall() + || Core::App().calls().currentGroupCall()) { return; } const auto width = bubbleWrap->width(); @@ -166,9 +168,13 @@ void Calls::setupContent() { bubbleWrap->update(); }, bubbleWrap->lifetime()); - Core::App().calls().currentCallValue( - ) | rpl::start_with_next([=](::Calls::Call *value) { - if (value) { + using namespace rpl::mappers; + rpl::combine( + Core::App().calls().currentCallValue(), + Core::App().calls().currentGroupCallValue(), + _1 || _2 + ) | rpl::start_with_next([=](bool has) { + if (has) { track->setState(VideoState::Inactive); bubbleWrap->resize(bubbleWrap->width(), 0); } else { From fb49b0ca270bc2d99b5eed84c8b5102e4ae8cfc8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 19 Apr 2021 13:41:32 +0400 Subject: [PATCH 069/477] Add proof-of-concept screen sharing on macOS. --- .../SourceFiles/calls/calls_group_call.cpp | 11 ++++++-- .../SourceFiles/calls/calls_group_members.cpp | 8 ++++-- Telegram/ThirdParty/tgcalls | 2 +- Telegram/cmake/lib_tgcalls.cmake | 28 ++++++++++--------- docs/building-msvc-x64.md | 12 +++++++- docs/building-msvc.md | 6 ++-- docs/building-xcode.md | 17 ++++++++++- 7 files changed, 60 insertions(+), 24 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index fb47c903c..988ba9599 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -383,7 +383,14 @@ GroupCall::GroupCall( GroupCall::~GroupCall() { destroyController(); - switchToCamera(); + const auto wasScreenSharing = isScreenSharing(); + const auto weak = wasScreenSharing + ? std::weak_ptr(_videoCapture) + : std::weak_ptr(); + _videoCapture = nullptr; + if (const auto strong = weak.lock()) { + strong->switchToDevice(_videoDeviceId.toStdString()); + } } bool GroupCall::isScreenSharing() const { @@ -402,7 +409,7 @@ void GroupCall::switchToScreenSharing() { if (isScreenSharing()) { return; } - _videoDeviceId = "desktop_capturer_"; + _videoDeviceId = "desktop_capturer_screen_0"; _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/calls_group_members.cpp index 1acdf4536..a4f539583 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/calls_group_members.cpp @@ -650,14 +650,16 @@ auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback { ? QSize(videoSize.width() * size / videoSize.height(), size) : QSize(size, videoSize.height() * size / videoSize.width()); const auto request = Webrtc::FrameRequest{ - .resize = resize, - .outer = QSize(size, size), + .resize = resize * cIntRetinaFactor(), + .outer = QSize(size, size) * cIntRetinaFactor(), }; const auto frame = _videoTrackShown->frame(request); auto copy = frame; // #TODO calls optimize. copy.detach(); Images::prepareCircle(copy); - p.drawImage(x, y, copy); + p.drawImage( + QRect(QPoint(x, y), copy.size() / cIntRetinaFactor()), + copy); _videoTrackShown->markFrameShown(); return; } else if (_videoTrackShown) { diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 082b938cb..01c03d7b4 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 082b938cbfef6539bdf6b71f9b46f9c2b2880d24 +Subproject commit 01c03d7b4cfd7c02c79bab32cc2e688316d1a450 diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 0d870dade..84c1d1ce2 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -139,19 +139,6 @@ PRIVATE reference/InstanceImplReference.h ) -remove_target_sources(lib_tgcalls ${tgcalls_loc} - desktop_capturer/DesktopCaptureSource.h - desktop_capturer/DesktopCaptureSource.mm - desktop_capturer/DesktopCaptureSourceHelper.h - desktop_capturer/DesktopCaptureSourceHelper.mm - desktop_capturer/DesktopCaptureSourceManager.h - desktop_capturer/DesktopCaptureSourceManager.mm - desktop_capturer/DesktopCaptureSourceView.h - desktop_capturer/DesktopCaptureSourceView.mm - desktop_capturer/DesktopSharingCapturer.h - desktop_capturer/DesktopSharingCapturer.mm -) - target_link_libraries(lib_tgcalls PRIVATE desktop-app::external_webrtc @@ -204,7 +191,22 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") ) endif() +if (NOT APPLE) + remove_target_sources(lib_tgcalls ${tgcalls_loc} + desktop_capturer/DesktopCaptureSource.h + desktop_capturer/DesktopCaptureSource.mm + desktop_capturer/DesktopCaptureSourceHelper.h + desktop_capturer/DesktopCaptureSourceHelper.mm + desktop_capturer/DesktopCaptureSourceManager.h + desktop_capturer/DesktopCaptureSourceManager.mm + desktop_capturer/DesktopSharingCapturer.h + desktop_capturer/DesktopSharingCapturer.mm + ) +endif() + remove_target_sources(lib_tgcalls ${tgcalls_loc} + desktop_capturer/DesktopCaptureSourceView.h + desktop_capturer/DesktopCaptureSourceView.mm platform/android/AndroidContext.cpp platform/android/AndroidContext.h platform/android/AndroidInterface.cpp diff --git a/docs/building-msvc-x64.md b/docs/building-msvc-x64.md index 92e91bdb1..a1bd70722 100644 --- a/docs/building-msvc-x64.md +++ b/docs/building-msvc-x64.md @@ -145,8 +145,18 @@ Open **x64 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** cd win32\VS2015 msbuild opus.sln /property:Configuration=Debug /property:Platform="x64" msbuild opus.sln /property:Configuration=Release /property:Platform="x64" + cd ..\..\.. - cd ..\..\..\..\.. + git clone https://github.com/desktop-app/rnnoise.git + cd rnnoise + mkdir out + cd out + cmake -A x64 .. + cmake --build . --config Debug + cmake --build . --config Release + cd ..\.. + + cd ..\.. SET PATH_BACKUP_=%PATH% SET PATH=%cd%\ThirdParty\msys64\usr\bin;%PATH% cd Libraries\win64 diff --git a/docs/building-msvc.md b/docs/building-msvc.md index e1739f366..c89d13ae6 100644 --- a/docs/building-msvc.md +++ b/docs/building-msvc.md @@ -145,18 +145,18 @@ Open **x86 Native Tools Command Prompt for VS 2019.bat**, go to ***BuildPath*** cd win32\VS2015 msbuild opus.sln /property:Configuration=Debug /property:Platform="Win32" msbuild opus.sln /property:Configuration=Release /property:Platform="Win32" + cd ..\..\.. git clone https://github.com/desktop-app/rnnoise.git cd rnnoise - git checkout tdesktop mkdir out cd out cmake -A Win32 .. cmake --build . --config Debug cmake --build . --config Release - cd .. + cd ..\.. - cd ..\..\..\.. + cd .. SET PATH_BACKUP_=%PATH% SET PATH=%cd%\ThirdParty\msys64\usr\bin;%PATH% cd Libraries diff --git a/docs/building-xcode.md b/docs/building-xcode.md index f4ac055f9..9a8269a64 100644 --- a/docs/building-xcode.md +++ b/docs/building-xcode.md @@ -97,7 +97,7 @@ Go to ***BuildPath*** and run make build_libs $MAKE_THREADS_CNT cd .. - git clone https://github.com/xiph/opus + git clone https://github.com/xiph/opus.git cd opus git checkout v1.3 ./autogen.sh @@ -106,6 +106,21 @@ Go to ***BuildPath*** and run sudo make install cd .. + git clone https://github.com/desktop-app/rnnoise.git + cd rnnoise + mkdir out + cd out + mkdir Debug + cd Debug + cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ../.. + ninja + cd .. + mkdir Release + cd Release + cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ../.. + ninja + cd ../../.. + libiconv_ver=1.16 wget https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$libiconv_ver.tar.gz tar -xvzf libiconv-$libiconv_ver.tar.gz From 9ff6b57b94067117c67a87ebea4a87096d918164 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 19 Apr 2021 19:41:14 +0400 Subject: [PATCH 070/477] Use common desktop_capturer code on Windows. --- .../SourceFiles/calls/calls_group_call.cpp | 11 ++++- .../SourceFiles/platform/win/windows_dlls.cpp | 22 ++++++++++ Telegram/ThirdParty/tgcalls | 2 +- Telegram/cmake/lib_tgcalls.cmake | 41 +++++++------------ 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index 988ba9599..e6a14abf3 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "webrtc/webrtc_media_devices.h" #include "webrtc/webrtc_create_adm.h" +#include #include #include #include @@ -409,8 +410,14 @@ void GroupCall::switchToScreenSharing() { if (isScreenSharing()) { return; } - _videoDeviceId = "desktop_capturer_screen_0"; - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + auto manager = tgcalls::DesktopCaptureSourceManager( + tgcalls::DesktopCaptureType::Screen); + const auto sources = manager.sources(); + if (!sources.empty()) { + const auto key = sources.front().deviceIdKey(); + _videoDeviceId = QString::fromStdString(key); + _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + } } void GroupCall::setScheduledDate(TimeId date) { diff --git a/Telegram/SourceFiles/platform/win/windows_dlls.cpp b/Telegram/SourceFiles/platform/win/windows_dlls.cpp index e88fad409..be756cfc5 100644 --- a/Telegram/SourceFiles/platform/win/windows_dlls.cpp +++ b/Telegram/SourceFiles/platform/win/windows_dlls.cpp @@ -12,6 +12,28 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include +#include + +HRESULT WINAPI D3D11CreateDevice( + _In_opt_ IDXGIAdapter* pAdapter, + D3D_DRIVER_TYPE DriverType, + HMODULE Software, + UINT Flags, + _In_reads_opt_(FeatureLevels) CONST D3D_FEATURE_LEVEL* pFeatureLevels, + UINT FeatureLevels, + UINT SDKVersion, + _COM_Outptr_opt_ ID3D11Device** ppDevice, + _Out_opt_ D3D_FEATURE_LEVEL* pFeatureLevel, + _COM_Outptr_opt_ ID3D11DeviceContext** ppImmediateContext) { + return S_FALSE; +} + +HRESULT WINAPI CreateDXGIFactory1( + REFIID riid, + _COM_Outptr_ void **ppFactory) { + return S_FALSE; +} + namespace Platform { namespace Dlls { diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 01c03d7b4..0f608993f 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 01c03d7b4cfd7c02c79bab32cc2e688316d1a450 +Subproject commit 0f608993f7a5c9af093f6a7bf21c3b4cd0735ecc diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 84c1d1ce2..fb465f4f1 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -58,15 +58,11 @@ PRIVATE # Desktop capturer desktop_capturer/DesktopCaptureSource.h - desktop_capturer/DesktopCaptureSource.mm + desktop_capturer/DesktopCaptureSource.cpp desktop_capturer/DesktopCaptureSourceHelper.h - desktop_capturer/DesktopCaptureSourceHelper.mm + desktop_capturer/DesktopCaptureSourceHelper.cpp desktop_capturer/DesktopCaptureSourceManager.h - desktop_capturer/DesktopCaptureSourceManager.mm - desktop_capturer/DesktopCaptureSourceView.h - desktop_capturer/DesktopCaptureSourceView.mm - desktop_capturer/DesktopSharingCapturer.h - desktop_capturer/DesktopSharingCapturer.mm + desktop_capturer/DesktopCaptureSourceManager.cpp # Group calls group/GroupInstanceCustomImpl.cpp @@ -91,6 +87,10 @@ PRIVATE # iOS / macOS platform/darwin/DarwinInterface.h platform/darwin/DarwinInterface.mm + platform/darwin/DesktopCaptureSourceView.h + platform/darwin/DesktopCaptureSourceView.mm + platform/darwin/DesktopSharingCapturer.h + platform/darwin/DesktopSharingCapturer.mm platform/darwin/GLVideoView.h platform/darwin/GLVideoView.mm platform/darwin/GLVideoViewMac.h @@ -123,8 +123,6 @@ PRIVATE # POSIX # Teleram Desktop - platform/tdesktop/DesktopCapturer.cpp - platform/tdesktop/DesktopCapturer.h platform/tdesktop/DesktopInterface.cpp platform/tdesktop/DesktopInterface.h platform/tdesktop/VideoCapturerInterfaceImpl.cpp @@ -167,6 +165,10 @@ elseif (APPLE) WEBRTC_MAC ) remove_target_sources(lib_tgcalls ${tgcalls_loc} + platform/darwin/DesktopCaptureSourceView.h + platform/darwin/DesktopCaptureSourceView.mm + platform/darwin/DesktopSharingCapturer.h + platform/darwin/DesktopSharingCapturer.mm platform/darwin/GLVideoView.h platform/darwin/GLVideoView.mm platform/darwin/GLVideoViewMac.h @@ -179,10 +181,12 @@ elseif (APPLE) platform/darwin/VideoMetalViewMac.mm platform/tdesktop/DesktopInterface.cpp platform/tdesktop/DesktopInterface.h - platform/tdesktop/VideoCapturerTrackSource.cpp - platform/tdesktop/VideoCapturerTrackSource.h platform/tdesktop/VideoCapturerInterfaceImpl.cpp platform/tdesktop/VideoCapturerInterfaceImpl.h + platform/tdesktop/VideoCapturerTrackSource.cpp + platform/tdesktop/VideoCapturerTrackSource.h + platform/tdesktop/VideoCameraCapturer.cpp + platform/tdesktop/VideoCameraCapturer.h ) elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") target_compile_definitions(lib_tgcalls @@ -191,22 +195,7 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") ) endif() -if (NOT APPLE) - remove_target_sources(lib_tgcalls ${tgcalls_loc} - desktop_capturer/DesktopCaptureSource.h - desktop_capturer/DesktopCaptureSource.mm - desktop_capturer/DesktopCaptureSourceHelper.h - desktop_capturer/DesktopCaptureSourceHelper.mm - desktop_capturer/DesktopCaptureSourceManager.h - desktop_capturer/DesktopCaptureSourceManager.mm - desktop_capturer/DesktopSharingCapturer.h - desktop_capturer/DesktopSharingCapturer.mm - ) -endif() - remove_target_sources(lib_tgcalls ${tgcalls_loc} - desktop_capturer/DesktopCaptureSourceView.h - desktop_capturer/DesktopCaptureSourceView.mm platform/android/AndroidContext.cpp platform/android/AndroidContext.h platform/android/AndroidInterface.cpp From c9d07cd0f8a6ff48fe2aaa6342500415b36cef48 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 20 Apr 2021 14:30:42 +0400 Subject: [PATCH 071/477] Proof-of-concept desktop capture source picker. --- Telegram/SourceFiles/calls/calls.style | 3 + .../SourceFiles/calls/calls_group_call.cpp | 15 +- Telegram/SourceFiles/calls/calls_group_call.h | 2 +- .../SourceFiles/calls/calls_group_panel.cpp | 15 +- .../SourceFiles/calls/calls_group_panel.h | 7 +- .../ui/desktop_capture_choose_source.cpp | 395 ++++++++++++++++++ .../group/ui/desktop_capture_choose_source.h | 28 ++ Telegram/cmake/td_ui.cmake | 5 + 8 files changed, 456 insertions(+), 14 deletions(-) create mode 100644 Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp create mode 100644 Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.h diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index b2201c1f9..1f58a45f8 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1015,3 +1015,6 @@ groupCallStartsInTop: 10px; groupCallStartsWhenTop: 160px; groupCallCountdownFont: font(64px semibold); groupCallCountdownTop: 52px; + +desktopCaptureSourceSize: size(160px, 120px); +desktopCaptureSourceSkip: 12px; diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index e6a14abf3..d568351b0 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -31,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "webrtc/webrtc_media_devices.h" #include "webrtc/webrtc_create_adm.h" -#include #include #include #include @@ -406,18 +405,12 @@ void GroupCall::switchToCamera() { _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } -void GroupCall::switchToScreenSharing() { - if (isScreenSharing()) { +void GroupCall::switchToScreenSharing(const QString &uniqueId) { + if (_videoDeviceId == uniqueId) { return; } - auto manager = tgcalls::DesktopCaptureSourceManager( - tgcalls::DesktopCaptureType::Screen); - const auto sources = manager.sources(); - if (!sources.empty()) { - const auto key = sources.front().deviceIdKey(); - _videoDeviceId = QString::fromStdString(key); - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); - } + _videoDeviceId = uniqueId; + _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } void GroupCall::setScheduledDate(TimeId date) { diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index e88f7c20d..0fa69f142 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -212,7 +212,7 @@ public: void setCurrentVideoDevice(const QString &deviceId); bool isScreenSharing() const; void switchToCamera(); - void switchToScreenSharing(); + void switchToScreenSharing(const QString &uniqueId); //void setAudioVolume(bool input, float level); void setAudioDuckingEnabled(bool enabled); diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index 7767c2029..bb589861e 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_group_members.h" #include "calls/calls_group_settings.h" #include "calls/calls_group_menu.h" +#include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/platform/ui_platform_utility.h" #include "ui/controls/call_mute_button.h" @@ -473,6 +474,18 @@ void Panel::subscribeToPeerChanges() { }, _peerLifetime); } +QWidget *Panel::chooseSourceParent() { + return _window.get(); +} + +rpl::lifetime &Panel::chooseSourceInstanceLifetime() { + return _window->lifetime(); +} + +void Panel::chooseSourceAccepted(const QString &deviceId) { + _call->switchToScreenSharing(deviceId); +} + void Panel::initWindow() { _window->setAttribute(Qt::WA_OpaquePaintEvent); _window->setAttribute(Qt::WA_NoSystemBackground); @@ -676,7 +689,7 @@ void Panel::refreshLeftButton() { if (_call->isScreenSharing()) { _call->switchToCamera(); } else { - _call->switchToScreenSharing(); + Ui::DesktopCapture::ChooseSource(this); } //_layerBg->showBox(Box(SettingsBox, _call)); }); diff --git a/Telegram/SourceFiles/calls/calls_group_panel.h b/Telegram/SourceFiles/calls/calls_group_panel.h index 71d68042c..e788e4df5 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/calls_group_panel.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" #include "calls/calls_group_call.h" #include "calls/calls_choose_join_as.h" +#include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" @@ -53,7 +54,7 @@ namespace Calls::Group { class Members; -class Panel final { +class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate { public: Panel(not_null call); ~Panel(); @@ -108,6 +109,10 @@ private: void migrate(not_null channel); void subscribeToPeerChanges(); + QWidget *chooseSourceParent() override; + rpl::lifetime &chooseSourceInstanceLifetime() override; + void chooseSourceAccepted(const QString &deviceId) override; + const not_null _call; not_null _peer; diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp new file mode 100644 index 000000000..73d4a2319 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp @@ -0,0 +1,395 @@ +/* +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 "calls/group/ui/desktop_capture_choose_source.h" + +#include "ui/widgets/window.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/buttons.h" +#include "base/platform/base_platform_info.h" +#include "webrtc/webrtc_video_track.h" +#include "styles/style_calls.h" + +#include +#include +#include + +namespace Calls::Group::Ui::DesktopCapture { +namespace { + +constexpr auto kColumns = 3; +constexpr auto kRows = 2; + +struct Preview { + explicit Preview(tgcalls::DesktopCaptureSource source); + + tgcalls::DesktopCaptureSourceHelper helper; + Webrtc::VideoTrack track; + rpl::lifetime lifetime; +}; + +class Source final { +public: + Source( + not_null parent, + tgcalls::DesktopCaptureSource source, + const QString &title); + + void setGeometry(QRect geometry); + void clearHelper(); + + [[nodiscard]] bool ready() const; + [[nodiscard]] rpl::producer<> clicks() const; + [[nodiscard]] rpl::lifetime &lifetime(); + +private: + void paint(); + void setupPreview(); + + AbstractButton _widget; + FlatLabel _label; + tgcalls::DesktopCaptureSource _source; + std::unique_ptr _preview; + QImage _frame; + +}; + +class ChooseSourceProcess final { +public: + static void Start(not_null delegate); + + explicit ChooseSourceProcess(not_null delegate); + + void activate(); + +private: + void setupPanel(); + void setupSources(); + void setupGeometryWithParent(not_null parent); + void fillSources(); + void setupSourcesGeometry(); + void destroy(); + + static base::flat_map< + not_null, + std::unique_ptr> &Map(); + + const not_null _delegate; + const std::unique_ptr<::Ui::Window> _window; + const std::unique_ptr _scroll; + const not_null _inner; + + std::vector> _sources; + +}; + +[[nodiscard]] tgcalls::DesktopCaptureSourceData SourceData() { + const auto factor = style::DevicePixelRatio(); + const auto size = st::desktopCaptureSourceSize * factor; + return { + .aspectSize = { size.width(), size.height() }, + .fps = 1, + .captureMouse = false, + }; +} + +Preview::Preview(tgcalls::DesktopCaptureSource source) +: helper(source, SourceData()) +, track(Webrtc::VideoState::Active) { + helper.setOutput(track.sink()); + helper.start(); +} + +Source::Source( + not_null parent, + tgcalls::DesktopCaptureSource source, + const QString &title) +: _widget(parent) +, _label(&_widget, title) +, _source(source) { + _widget.paintRequest( + ) | rpl::start_with_next([=] { + paint(); + }, _widget.lifetime()); + + _widget.sizeValue( + ) | rpl::start_with_next([=](QSize size) { + _label.resizeToNaturalWidth(size.width()); + _label.move( + (size.width() - _label.width()) / 2, + size.height() - _label.height()); + }, _label.lifetime()); +} + +rpl::producer<> Source::clicks() const { + return _widget.clicks() | rpl::to_empty; +} + +void Source::setGeometry(QRect geometry) { + _widget.setGeometry(geometry); +} + +void Source::clearHelper() { + _preview = nullptr; +} + +void Source::paint() { + auto p = QPainter(&_widget); + + if (_frame.isNull() && !_preview) { + setupPreview(); + } + const auto size = _preview ? _preview->track.frameSize() : QSize(); + const auto factor = style::DevicePixelRatio(); + const auto rect = _widget.rect(); + const auto inner = QRect( + rect.x(), + rect.y(), + rect.width(), + rect.height() - _label.height()); + if (!size.isEmpty()) { + const auto scaled = size.scaled(inner.size(), Qt::KeepAspectRatio); + const auto request = Webrtc::FrameRequest{ + .resize = scaled * factor, + .outer = scaled * factor, + }; + _frame = _preview->track.frame(request); + _preview->track.markFrameShown(); + } + if (!_frame.isNull()) { + clearHelper(); + const auto size = _frame.size() / factor; + const auto x = inner.x() + (inner.width() - size.width()) / 2; + const auto y = inner.y() + (inner.height() - size.height()) / 2; + auto hq = PainterHighQualityEnabler(p); + p.drawImage(QRect(x, y, size.width(), size.height()), _frame); + } +} + +void Source::setupPreview() { + _preview = std::make_unique(_source); + _preview->track.renderNextFrame( + ) | rpl::start_with_next([=] { + if (_preview->track.frameSize().isEmpty()) { + _preview->track.markFrameShown(); + } else { + _widget.update(); + } + }, _preview->lifetime); +} + +rpl::lifetime &Source::lifetime() { + return _widget.lifetime(); +} + +ChooseSourceProcess::ChooseSourceProcess( + not_null delegate) +: _delegate(delegate) +, _window(std::make_unique<::Ui::Window>()) +, _scroll(std::make_unique(_window->body())) +, _inner(_scroll->setOwnedWidget(object_ptr(_scroll.get()))) { + setupPanel(); + setupSources(); + activate(); +} + +void ChooseSourceProcess::Start(not_null delegate) { + auto &map = Map(); + auto i = map.find(delegate); + if (i == end(map)) { + i = map.emplace(delegate, nullptr).first; + delegate->chooseSourceInstanceLifetime().add([=] { + Map().erase(delegate); + }); + } + if (!i->second) { + i->second = std::make_unique(delegate); + } else { + i->second->activate(); + } +} + +void ChooseSourceProcess::activate() { + if (_window->windowState() & Qt::WindowMinimized) { + _window->showNormal(); + } else { + _window->show(); + } + _window->activateWindow(); +} + +[[nodiscard]] base::flat_map< + not_null, + std::unique_ptr> &ChooseSourceProcess::Map() { + static auto result = base::flat_map< + not_null, + std::unique_ptr>(); + return result; +} + +void ChooseSourceProcess::setupPanel() { + const auto width = kColumns * st::desktopCaptureSourceSize.width() + + (kColumns + 1) * st::desktopCaptureSourceSkip; + const auto height = kRows * st::desktopCaptureSourceSize.height() + + (kRows + 1) * st::desktopCaptureSourceSkip + + (st::desktopCaptureSourceSize.height() / 2); + _window->setFixedSize({ width, height }); + _window->setWindowFlags(Qt::WindowStaysOnTopHint); + + _window->body()->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + _scroll->setGeometry({ QPoint(), size }); + }, _scroll->lifetime()); + + _scroll->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto rows = int(std::ceil(_sources.size() / float(kColumns))); + const auto height = rows * st::desktopCaptureSourceSize.height() + + (rows + 1) * st::desktopCaptureSourceSkip; + _inner->resize(width, height); + }, _inner->lifetime()); + + if (const auto parent = _delegate->chooseSourceParent()) { + setupGeometryWithParent(parent); + } + + _window->events( + ) | rpl::filter([=](not_null e) { + return e->type() == QEvent::Close; + }) | rpl::start_with_next([=] { + destroy(); + }, _window->lifetime()); +} + +void ChooseSourceProcess::setupSources() { + fillSources(); + setupSourcesGeometry(); +} + +void ChooseSourceProcess::fillSources() { + using Type = tgcalls::DesktopCaptureType; + auto screensManager = tgcalls::DesktopCaptureSourceManager(Type::Screen); + auto windowsManager = tgcalls::DesktopCaptureSourceManager(Type::Window); + + auto screenIndex = 0; + auto windowIndex = 0; + const auto append = [&](const tgcalls::DesktopCaptureSource &source) { + const auto title = !source.title().empty() + ? QString::fromStdString(source.title()) + : source.isWindow() + ? "Window " + QString::number(++windowIndex) + : "Screen " + QString::number(++screenIndex); + _sources.push_back(std::make_unique(_inner, source, title)); + _sources.back()->clicks( + ) | rpl::start_with_next([=, id = source.deviceIdKey()]{ + _delegate->chooseSourceAccepted(QString::fromStdString(id)); + }, _sources.back()->lifetime()); + }; + for (const auto &source : screensManager.sources()) { + append(source); + } + for (const auto &source : windowsManager.sources()) { + append(source); + } +} + +void ChooseSourceProcess::setupSourcesGeometry() { + if (_sources.empty()) { + //LOG(()); + destroy(); + return; + } + _inner->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto rows = int(std::ceil(_sources.size() / float(kColumns))); + const auto skip = st::desktopCaptureSourceSkip; + const auto single = (width - (kColumns + 1) * skip) / kColumns; + const auto height = st::desktopCaptureSourceSize.height(); + auto top = skip; + auto index = 0; + for (auto row = 0; row != rows; ++row) { + auto left = skip; + for (auto column = 0; column != kColumns; ++column) { + _sources[index]->setGeometry({ left, top, single, height }); + if (++index == _sources.size()) { + break; + } + left += single + skip; + } + if (index >= _sources.size()) { + break; + } + top += height + skip; + } + }, _inner->lifetime()); + + rpl::combine( + _scroll->scrollTopValue(), + _scroll->heightValue() + ) | rpl::start_with_next([=](int scrollTop, int scrollHeight) { + const auto rows = int(std::ceil(_sources.size() / float(kColumns))); + const auto skip = st::desktopCaptureSourceSkip; + const auto height = st::desktopCaptureSourceSize.height(); + auto top = skip; + auto index = 0; + for (auto row = 0; row != rows; ++row) { + const auto hidden = (top + height <= scrollTop) + || (top >= scrollTop + scrollHeight); + if (hidden) { + for (auto column = 0; column != kColumns; ++column) { + _sources[index]->clearHelper(); + if (++index == _sources.size()) { + break; + } + } + } else { + index += kColumns; + } + if (index >= _sources.size()) { + break; + } + top += height + skip; + } + }, _inner->lifetime()); +} + +void ChooseSourceProcess::setupGeometryWithParent( + not_null parent) { + if (const auto handle = parent->windowHandle()) { + if (::Platform::IsLinux()) { + _window->windowHandle()->setTransientParent( + parent->windowHandle()); + _window->setWindowModality(Qt::WindowModal); + } + const auto parentScreen = handle->screen(); + const auto myScreen = _window->windowHandle()->screen(); + if (parentScreen && myScreen != parentScreen) { + _window->windowHandle()->setScreen(parentScreen); + } + } + _window->move( + parent->x() + (parent->width() - _window->width()) / 2, + parent->y() + (parent->height() - _window->height()) / 2); +} + +void ChooseSourceProcess::destroy() { + auto &map = Map(); + if (const auto i = map.find(_delegate); i != end(map)) { + if (i->second.get() == this) { + base::take(i->second); + } + } +} + +} // namespace + +void ChooseSource(not_null delegate) { + ChooseSourceProcess::Start(delegate); +} + +} // namespace Calls::Group::Ui::DesktopCapture diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.h b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.h new file mode 100644 index 000000000..22b871fa8 --- /dev/null +++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.h @@ -0,0 +1,28 @@ +/* +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 +*/ +#pragma once + +namespace Ui { +} // namespace Ui + +namespace Calls::Group::Ui { +using namespace ::Ui; +} // namespace Calls::Group::Ui + +namespace Calls::Group::Ui::DesktopCapture { + +class ChooseSourceDelegate { +public: + virtual QWidget *chooseSourceParent() = 0; + virtual rpl::lifetime &chooseSourceInstanceLifetime() = 0; + virtual void chooseSourceAccepted(const QString &deviceId) = 0; +}; + +void ChooseSource(not_null delegate); + +} // namespace Calls::Group::Ui::DesktopCapture diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 75d522651..28d27d2df 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -47,6 +47,9 @@ nice_target_sources(td_ui ${src_loc} PRIVATE ${style_files} + calls/group/ui/desktop_capture_choose_source.cpp + calls/group/ui/desktop_capture_choose_source.h + core/file_location.cpp core/file_location.h core/mime_type.cpp @@ -163,7 +166,9 @@ PUBLIC desktop-app::lib_ui desktop-app::lib_lottie PRIVATE + tdesktop::lib_tgcalls desktop-app::lib_ffmpeg desktop-app::lib_webview + desktop-app::lib_webrtc desktop-app::lib_stripe ) From 6e34360f7e6e216f0d5492eed0412d4944f4b4fa Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 22 Apr 2021 13:13:13 +0400 Subject: [PATCH 072/477] Move some group call modules. --- Telegram/CMakeLists.txt | 30 ++++---- Telegram/SourceFiles/boxes/peer_list_box.cpp | 75 ++++++++++++++----- Telegram/SourceFiles/boxes/peer_list_box.h | 26 ++++++- Telegram/SourceFiles/calls/calls.style | 4 + Telegram/SourceFiles/calls/calls_instance.cpp | 6 +- Telegram/SourceFiles/calls/calls_instance.h | 4 +- Telegram/SourceFiles/calls/calls_top_bar.cpp | 2 +- .../{ => group}/calls_choose_join_as.cpp | 6 +- .../calls/{ => group}/calls_choose_join_as.h | 0 .../calls/{ => group}/calls_group_call.cpp | 4 +- .../calls/{ => group}/calls_group_call.h | 0 .../calls/{ => group}/calls_group_common.h | 0 .../calls/{ => group}/calls_group_members.cpp | 40 ++++++++-- .../calls/{ => group}/calls_group_members.h | 0 .../calls/{ => group}/calls_group_menu.cpp | 8 +- .../calls/{ => group}/calls_group_menu.h | 0 .../calls/{ => group}/calls_group_panel.cpp | 10 +-- .../calls/{ => group}/calls_group_panel.h | 4 +- .../{ => group}/calls_group_settings.cpp | 10 +-- .../calls/{ => group}/calls_group_settings.h | 0 .../calls/{ => group}/calls_volume_item.cpp | 4 +- .../calls/{ => group}/calls_volume_item.h | 0 Telegram/SourceFiles/data/data_group_call.cpp | 4 +- .../view/history_view_group_call_tracker.cpp | 2 +- 24 files changed, 168 insertions(+), 71 deletions(-) rename Telegram/SourceFiles/calls/{ => group}/calls_choose_join_as.cpp (98%) rename Telegram/SourceFiles/calls/{ => group}/calls_choose_join_as.h (100%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_call.cpp (99%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_call.h (100%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_common.h (100%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_members.cpp (98%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_members.h (100%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_menu.cpp (99%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_menu.h (100%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_panel.cpp (99%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_panel.h (97%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_settings.cpp (98%) rename Telegram/SourceFiles/calls/{ => group}/calls_group_settings.h (100%) rename Telegram/SourceFiles/calls/{ => group}/calls_volume_item.cpp (98%) rename Telegram/SourceFiles/calls/{ => group}/calls_volume_item.h (100%) diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d3193cb90..e7322b974 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -272,23 +272,25 @@ PRIVATE boxes/url_auth_box.h boxes/username_box.cpp boxes/username_box.h + calls/group/calls_choose_join_as.cpp + calls/group/calls_choose_join_as.h + calls/group/calls_group_call.cpp + calls/group/calls_group_call.h + calls/group/calls_group_common.h + calls/group/calls_group_members.cpp + calls/group/calls_group_members.h + calls/group/calls_group_menu.cpp + calls/group/calls_group_menu.h + calls/group/calls_group_panel.cpp + calls/group/calls_group_panel.h + calls/group/calls_group_settings.cpp + calls/group/calls_group_settings.h + calls/group/calls_volume_item.cpp + calls/group/calls_volume_item.h calls/calls_box_controller.cpp calls/calls_box_controller.h calls/calls_call.cpp calls/calls_call.h - calls/calls_choose_join_as.cpp - calls/calls_choose_join_as.h - calls/calls_group_call.cpp - calls/calls_group_call.h - calls/calls_group_common.h - calls/calls_group_members.cpp - calls/calls_group_members.h - calls/calls_group_menu.cpp - calls/calls_group_menu.h - calls/calls_group_panel.cpp - calls/calls_group_panel.h - calls/calls_group_settings.cpp - calls/calls_group_settings.h calls/calls_emoji_fingerprint.cpp calls/calls_emoji_fingerprint.h calls/calls_instance.cpp @@ -303,8 +305,6 @@ PRIVATE calls/calls_userpic.h calls/calls_video_bubble.cpp calls/calls_video_bubble.h - calls/calls_volume_item.cpp - calls/calls_volume_item.h chat_helpers/bot_keyboard.cpp chat_helpers/bot_keyboard.h chat_helpers/emoji_keywords.cpp diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index e1858946d..c0b8d6d5f 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -741,6 +741,32 @@ PeerListContent::PeerListContent( _repaintByStatus.setCallback([this] { update(); }); } +void PeerListContent::setMode(Mode mode) { + if (mode == Mode::Default && _mode == Mode::Default) { + return; + } + _mode = mode; + switch (_mode) { + case Mode::Default: + _rowHeight = _st.item.height; + break; + case Mode::Custom: + _rowHeight = _controller->customRowHeight(); + break; + } + const auto wasMouseSelection = _mouseSelection; + const auto wasLastMousePosition = _lastMousePosition; + _contextMenu = nullptr; + if (wasMouseSelection) { + setSelected(Selected()); + } + setPressed(Selected()); + refreshRows(); + if (wasMouseSelection && wasLastMousePosition) { + selectByMouse(*wasLastMousePosition); + } +} + void PeerListContent::appendRow(std::unique_ptr row) { Expects(row != nullptr); @@ -1080,25 +1106,25 @@ void PeerListContent::clearSearchRows() { void PeerListContent::paintEvent(QPaintEvent *e) { Painter p(this); - auto clip = e->rect(); + const auto clip = e->rect(); p.fillRect(clip, _st.item.button.textBg); - auto repaintByStatusAfter = _repaintByStatus.remainingTime(); + const auto repaintByStatusAfter = _repaintByStatus.remainingTime(); auto repaintAfterMin = repaintByStatusAfter; - auto rowsTopCached = rowsTop(); - auto ms = crl::now(); - auto yFrom = clip.y() - rowsTopCached; - auto yTo = clip.y() + clip.height() - rowsTopCached; + const auto rowsTopCached = rowsTop(); + const auto now = crl::now(); + const auto yFrom = clip.y() - rowsTopCached; + const auto yTo = clip.y() + clip.height() - rowsTopCached; p.translate(0, rowsTopCached); - auto count = shownRowsCount(); + const auto count = shownRowsCount(); if (count > 0) { - auto from = floorclamp(yFrom, _rowHeight, 0, count); - auto to = ceilclamp(yTo, _rowHeight, 0, count); + const auto from = floorclamp(yFrom, _rowHeight, 0, count); + const auto to = ceilclamp(yTo, _rowHeight, 0, count); p.translate(0, from * _rowHeight); for (auto index = from; index != to; ++index) { - auto repaintAfter = paintRow(p, ms, RowIndex(index)); - if (repaintAfter >= 0 + const auto repaintAfter = paintRow(p, now, RowIndex(index)); + if (repaintAfter > 0 && (repaintAfterMin < 0 || repaintAfterMin > repaintAfter)) { repaintAfterMin = repaintAfter; @@ -1307,7 +1333,7 @@ void PeerListContent::setPressed(Selected pressed) { crl::time PeerListContent::paintRow( Painter &p, - crl::time ms, + crl::time now, RowIndex index) { const auto row = getRow(index); Assert(row != nullptr); @@ -1315,13 +1341,15 @@ crl::time PeerListContent::paintRow( row->lazyInitialize(_st.item); auto refreshStatusAt = row->refreshStatusTime(); - if (refreshStatusAt >= 0 && ms >= refreshStatusAt) { + if (refreshStatusAt > 0 && now >= refreshStatusAt) { row->refreshStatus(); refreshStatusAt = row->refreshStatusTime(); } + const auto refreshStatusIn = (refreshStatusAt > 0) + ? std::max(refreshStatusAt - now, crl::time(1)) + : 0; const auto peer = row->special() ? nullptr : row->peer().get(); - const auto user = peer ? peer->asUser() : nullptr; const auto active = (_contexted.index.value >= 0) ? _contexted : (_pressed.index.value >= 0) @@ -1330,6 +1358,11 @@ crl::time PeerListContent::paintRow( const auto selected = (active.index == index); const auto actionSelected = (selected && active.action); + if (_mode == Mode::Custom) { + _controller->customRowPaint(p, now, row, selected); + return refreshStatusIn; + } + const auto &bg = selected ? _st.item.button.textBgOver : _st.item.button.textBg; @@ -1412,7 +1445,7 @@ crl::time PeerListContent::paintRow( } else { row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected); } - return (refreshStatusAt - ms); + return refreshStatusIn; } PeerListContent::SkipResult PeerListContent::selectSkip(int direction) { @@ -1740,15 +1773,21 @@ void PeerListContent::selectByMouse(QPoint globalPosition) { _mouseSelection = true; _lastMousePosition = globalPosition; const auto point = mapFromGlobal(globalPosition); + const auto customMode = (_mode == Mode::Custom); auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(globalPosition)); auto selected = Selected(); auto rowsPointY = point.y() - rowsTop(); selected.index.value = (in && rowsPointY >= 0 && rowsPointY < shownRowsCount() * _rowHeight) ? (rowsPointY / _rowHeight) : -1; if (selected.index.value >= 0) { - auto row = getRow(selected.index); - if (row->disabled()) { + const auto row = getRow(selected.index); + if (row->disabled() + || (customMode + && !_controller->customRowSelectionPoint( + row, + point.x(), + rowsPointY))) { selected = Selected(); - } else { + } else if (!customMode) { if (getActiveActionRect(row, selected.index).contains(point)) { selected.action = true; } diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 3c5c139a6..32b6b26e5 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -450,6 +450,23 @@ public: [[nodiscard]] virtual bool respectSavedMessagesChat() const { return false; } + [[nodiscard]] virtual int customRowHeight() { + Unexpected("Unimplemented PeerListController::customRowHeight."); + } + virtual void customRowPaint( + Painter &p, + crl::time now, + not_null row, + bool selected) { + Unexpected("Unimplemented PeerListController::customRowPaint."); + } + [[nodiscard]] virtual bool customRowSelectionPoint( + not_null row, + int x, + int y) { + Unexpected( + "Unimplemented PeerListController::customRowSelectionPoint."); + } [[nodiscard]] virtual rpl::producer onlineCountValue() const; @@ -514,6 +531,12 @@ public: SkipResult selectSkip(int direction); void selectSkipPage(int height, int direction); + enum class Mode { + Default, + Custom, + }; + void setMode(Mode mode); + [[nodiscard]] rpl::producer selectedIndexValue() const; [[nodiscard]] bool hasSelection() const; [[nodiscard]] bool hasPressed() const; @@ -658,7 +681,7 @@ private: QPoint globalPos, Fn)> destroyed = nullptr); - crl::time paintRow(Painter &p, crl::time ms, RowIndex index); + crl::time paintRow(Painter &p, crl::time now, RowIndex index); void addRowEntry(not_null row); void addToSearchIndex(not_null row); @@ -688,6 +711,7 @@ private: not_null _controller; PeerListSearchMode _searchMode = PeerListSearchMode::Disabled; + Mode _mode = Mode::Default; int _rowHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 1f58a45f8..f4e7390e3 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1018,3 +1018,7 @@ groupCallCountdownTop: 52px; desktopCaptureSourceSize: size(160px, 120px); desktopCaptureSourceSkip: 12px; + +groupCallNarrowSkip: 9px; +groupCallNarrowRowSkip: 8px; +groupCallNarrowSize: size(90px, 90px); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index b59a9f42b..9832b77a1 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_instance.h" -#include "calls/calls_group_common.h" +#include "calls/group/calls_group_common.h" #include "mtproto/mtproto_dh_utils.h" #include "core/application.h" #include "main/main_session.h" @@ -15,10 +15,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "lang/lang_keys.h" #include "boxes/confirm_box.h" +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_panel.h" #include "calls/calls_call.h" -#include "calls/calls_group_call.h" #include "calls/calls_panel.h" -#include "calls/calls_group_panel.h" #include "data/data_user.h" #include "data/data_group_call.h" #include "data/data_channel.h" diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index c7e66a47b..1c508c1d8 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -9,8 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" #include "calls/calls_call.h" -#include "calls/calls_group_call.h" -#include "calls/calls_choose_join_as.h" +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_choose_join_as.h" namespace Platform { enum class PermissionType; diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index f598bb20b..f0725ecdf 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -22,7 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_call.h" #include "calls/calls_instance.h" #include "calls/calls_signal_bars.h" -#include "calls/calls_group_menu.h" // Group::LeaveBox. +#include "calls/group/calls_group_menu.h" // Group::LeaveBox. #include "history/view/history_view_group_call_tracker.h" // ContentByCall. #include "data/data_user.h" #include "data/data_group_call.h" diff --git a/Telegram/SourceFiles/calls/calls_choose_join_as.cpp b/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp similarity index 98% rename from Telegram/SourceFiles/calls/calls_choose_join_as.cpp rename to Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp index d58850b4b..5f7bec0e3 100644 --- a/Telegram/SourceFiles/calls/calls_choose_join_as.cpp +++ b/Telegram/SourceFiles/calls/group/calls_choose_join_as.cpp @@ -5,10 +5,10 @@ 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 "calls/calls_choose_join_as.h" +#include "calls/group/calls_choose_join_as.h" -#include "calls/calls_group_common.h" -#include "calls/calls_group_menu.h" +#include "calls/group/calls_group_common.h" +#include "calls/group/calls_group_menu.h" #include "data/data_peer.h" #include "data/data_user.h" #include "data/data_channel.h" diff --git a/Telegram/SourceFiles/calls/calls_choose_join_as.h b/Telegram/SourceFiles/calls/group/calls_choose_join_as.h similarity index 100% rename from Telegram/SourceFiles/calls/calls_choose_join_as.h rename to Telegram/SourceFiles/calls/group/calls_choose_join_as.h diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp similarity index 99% rename from Telegram/SourceFiles/calls/calls_group_call.cpp rename to Telegram/SourceFiles/calls/group/calls_group_call.cpp index d568351b0..3f076471e 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -5,9 +5,9 @@ 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 "calls/calls_group_call.h" +#include "calls/group/calls_group_call.h" -#include "calls/calls_group_common.h" +#include "calls/group/calls_group_common.h" #include "main/main_session.h" #include "api/api_send_progress.h" #include "api/api_updates.h" diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h similarity index 100% rename from Telegram/SourceFiles/calls/calls_group_call.h rename to Telegram/SourceFiles/calls/group/calls_group_call.h diff --git a/Telegram/SourceFiles/calls/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h similarity index 100% rename from Telegram/SourceFiles/calls/calls_group_common.h rename to Telegram/SourceFiles/calls/group/calls_group_common.h diff --git a/Telegram/SourceFiles/calls/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp similarity index 98% rename from Telegram/SourceFiles/calls/calls_group_members.cpp rename to Telegram/SourceFiles/calls/group/calls_group_members.cpp index a4f539583..e5317d777 100644 --- a/Telegram/SourceFiles/calls/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -5,12 +5,12 @@ 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 "calls/calls_group_members.h" +#include "calls/group/calls_group_members.h" -#include "calls/calls_group_call.h" -#include "calls/calls_group_common.h" -#include "calls/calls_group_menu.h" -#include "calls/calls_volume_item.h" +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_common.h" +#include "calls/group/calls_group_menu.h" +#include "calls/group/calls_volume_item.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_user.h" @@ -305,6 +305,17 @@ public: QRect rect, IconState state) override; + int customRowHeight() override; + void customRowPaint( + Painter &p, + crl::time now, + not_null row, + bool selected) override; + bool customRowSelectionPoint( + not_null row, + int x, + int y) override; + private: [[nodiscard]] std::unique_ptr createRowForMe(); [[nodiscard]] std::unique_ptr createRow( @@ -1563,6 +1574,25 @@ void MembersController::rowPaintIcon( _inactiveCrossLine.paint(p, left, top, crossProgress, iconColor); } +int MembersController::customRowHeight() { + return st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip; +} + +void MembersController::customRowPaint( + Painter &p, + crl::time now, + not_null row, + bool selected) { + const auto real = static_cast(row.get()); +} + +bool MembersController::customRowSelectionPoint( + not_null row, + int x, + int y) { + return y < st::groupCallNarrowSize.height(); +} + auto MembersController::kickParticipantRequests() const -> rpl::producer>{ return _kickParticipantRequests.events(); diff --git a/Telegram/SourceFiles/calls/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h similarity index 100% rename from Telegram/SourceFiles/calls/calls_group_members.h rename to Telegram/SourceFiles/calls/group/calls_group_members.h diff --git a/Telegram/SourceFiles/calls/calls_group_menu.cpp b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp similarity index 99% rename from Telegram/SourceFiles/calls/calls_group_menu.cpp rename to Telegram/SourceFiles/calls/group/calls_group_menu.cpp index 3887503bc..081b15fdb 100644 --- a/Telegram/SourceFiles/calls/calls_group_menu.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_menu.cpp @@ -5,11 +5,11 @@ 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 "calls/calls_group_menu.h" +#include "calls/group/calls_group_menu.h" -#include "calls/calls_group_call.h" -#include "calls/calls_group_settings.h" -#include "calls/calls_group_panel.h" +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_settings.h" +#include "calls/group/calls_group_panel.h" #include "data/data_peer.h" #include "data/data_group_call.h" #include "info/profile/info_profile_values.h" // Info::Profile::NameValue. diff --git a/Telegram/SourceFiles/calls/calls_group_menu.h b/Telegram/SourceFiles/calls/group/calls_group_menu.h similarity index 100% rename from Telegram/SourceFiles/calls/calls_group_menu.h rename to Telegram/SourceFiles/calls/group/calls_group_menu.h diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp similarity index 99% rename from Telegram/SourceFiles/calls/calls_group_panel.cpp rename to Telegram/SourceFiles/calls/group/calls_group_panel.cpp index bb589861e..7b7485d9f 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -5,12 +5,12 @@ 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 "calls/calls_group_panel.h" +#include "calls/group/calls_group_panel.h" -#include "calls/calls_group_common.h" -#include "calls/calls_group_members.h" -#include "calls/calls_group_settings.h" -#include "calls/calls_group_menu.h" +#include "calls/group/calls_group_common.h" +#include "calls/group/calls_group_members.h" +#include "calls/group/calls_group_settings.h" +#include "calls/group/calls_group_menu.h" #include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/platform/ui_platform_window_title.h" #include "ui/platform/ui_platform_utility.h" diff --git a/Telegram/SourceFiles/calls/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h similarity index 97% rename from Telegram/SourceFiles/calls/calls_group_panel.h rename to Telegram/SourceFiles/calls/group/calls_group_panel.h index e788e4df5..2ae20b525 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -10,8 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/weak_ptr.h" #include "base/timer.h" #include "base/object_ptr.h" -#include "calls/calls_group_call.h" -#include "calls/calls_choose_join_as.h" +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_choose_join_as.h" #include "calls/group/ui/desktop_capture_choose_source.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" diff --git a/Telegram/SourceFiles/calls/calls_group_settings.cpp b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp similarity index 98% rename from Telegram/SourceFiles/calls/calls_group_settings.cpp rename to Telegram/SourceFiles/calls/group/calls_group_settings.cpp index 9ae8bbcae..cbea713c7 100644 --- a/Telegram/SourceFiles/calls/calls_group_settings.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_settings.cpp @@ -5,13 +5,13 @@ 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 "calls/calls_group_settings.h" +#include "calls/group/calls_group_settings.h" -#include "calls/calls_group_call.h" -#include "calls/calls_group_menu.h" // LeaveBox. -#include "calls/calls_group_common.h" +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_menu.h" // LeaveBox. +#include "calls/group/calls_group_common.h" +#include "calls/group/calls_choose_join_as.h" #include "calls/calls_instance.h" -#include "calls/calls_choose_join_as.h" #include "ui/widgets/level_meter.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/buttons.h" diff --git a/Telegram/SourceFiles/calls/calls_group_settings.h b/Telegram/SourceFiles/calls/group/calls_group_settings.h similarity index 100% rename from Telegram/SourceFiles/calls/calls_group_settings.h rename to Telegram/SourceFiles/calls/group/calls_group_settings.h diff --git a/Telegram/SourceFiles/calls/calls_volume_item.cpp b/Telegram/SourceFiles/calls/group/calls_volume_item.cpp similarity index 98% rename from Telegram/SourceFiles/calls/calls_volume_item.cpp rename to Telegram/SourceFiles/calls/group/calls_volume_item.cpp index 3d1cc249c..799814d9d 100644 --- a/Telegram/SourceFiles/calls/calls_volume_item.cpp +++ b/Telegram/SourceFiles/calls/group/calls_volume_item.cpp @@ -5,9 +5,9 @@ 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 "calls/calls_volume_item.h" +#include "calls/group/calls_volume_item.h" -#include "calls/calls_group_common.h" +#include "calls/group/calls_group_common.h" #include "ui/effects/animation_value.h" #include "ui/effects/cross_line.h" #include "ui/widgets/continuous_sliders.h" diff --git a/Telegram/SourceFiles/calls/calls_volume_item.h b/Telegram/SourceFiles/calls/group/calls_volume_item.h similarity index 100% rename from Telegram/SourceFiles/calls/calls_volume_item.h rename to Telegram/SourceFiles/calls/group/calls_volume_item.h diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 061a7fe7b..9862936b5 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -14,8 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "main/main_session.h" #include "calls/calls_instance.h" -#include "calls/calls_group_call.h" -#include "calls/calls_group_common.h" +#include "calls/group/calls_group_call.h" +#include "calls/group/calls_group_common.h" #include "core/application.h" #include "apiwrap.h" diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp index 4d9f0f1f1..1f7e5d5fe 100644 --- a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/group_call_bar.h" #include "ui/chat/group_call_userpics.h" #include "ui/painter.h" -#include "calls/calls_group_call.h" +#include "calls/group/calls_group_call.h" #include "calls/calls_instance.h" #include "core/application.h" #include "styles/style_chat.h" From c93ddf6aac13ce0bded5d5e6c00a966258dbc36f Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 22 Apr 2021 20:05:17 +0400 Subject: [PATCH 073/477] Separate Call/GroupCall delegates and Instance. --- .../Resources/icons/calls/call_settings.png | Bin 345 -> 0 bytes .../icons/calls/call_settings@2x.png | Bin 591 -> 0 bytes .../icons/calls/call_settings@3x.png | Bin 932 -> 0 bytes .../Resources/icons/calls/calls_settings.png | Bin 0 -> 756 bytes .../icons/calls/calls_settings@2x.png | Bin 0 -> 1501 bytes .../icons/calls/calls_settings@3x.png | Bin 0 -> 2200 bytes Telegram/SourceFiles/boxes/peer_list_box.cpp | 4 +- Telegram/SourceFiles/calls/calls.style | 4 +- Telegram/SourceFiles/calls/calls_call.cpp | 8 +- Telegram/SourceFiles/calls/calls_call.h | 16 +- Telegram/SourceFiles/calls/calls_instance.cpp | 216 ++++++++++++------ Telegram/SourceFiles/calls/calls_instance.h | 63 ++--- Telegram/SourceFiles/calls/calls_top_bar.cpp | 1 + .../calls/group/calls_group_call.cpp | 139 ++++++----- .../calls/group/calls_group_call.h | 37 ++- .../calls/group/calls_group_common.h | 5 + .../calls/group/calls_group_members.cpp | 72 ++++-- .../calls/group/calls_group_members.h | 4 + .../calls/group/calls_group_panel.cpp | 74 ++++-- .../calls/group/calls_group_panel.h | 3 + Telegram/SourceFiles/mainwidget.cpp | 2 + .../SourceFiles/settings/settings_calls.cpp | 35 +-- .../window/window_session_controller.cpp | 1 + 23 files changed, 422 insertions(+), 262 deletions(-) delete mode 100644 Telegram/Resources/icons/calls/call_settings.png delete mode 100644 Telegram/Resources/icons/calls/call_settings@2x.png delete mode 100644 Telegram/Resources/icons/calls/call_settings@3x.png create mode 100644 Telegram/Resources/icons/calls/calls_settings.png create mode 100644 Telegram/Resources/icons/calls/calls_settings@2x.png create mode 100644 Telegram/Resources/icons/calls/calls_settings@3x.png diff --git a/Telegram/Resources/icons/calls/call_settings.png b/Telegram/Resources/icons/calls/call_settings.png deleted file mode 100644 index f16037f3c3f1d7a366c51d614587e000004ecc8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 345 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmSN`?>!lvI6-QsS%!O zzP=1vKsE;hV|yk83y{SK#8N=az`(SC2`(bBfEmFCNgn(gJ{?G%@pN$v@!&lh!pPU6 zz#}a_|9J7k$UTk4ma})PY+mWz*Rr5*>+5SXI##T=&Z#{#!TiC0HI1bL`PoZNE2`P{ z{tpUHn(PcGNv!a&utFv?3v9n zqx0z`?G zwG30T!{g5TzXu;Z`h|Tk)LH_e} L^>bP0l+XkK+JScQ diff --git a/Telegram/Resources/icons/calls/call_settings@2x.png b/Telegram/Resources/icons/calls/call_settings@2x.png deleted file mode 100644 index 21d18e24feb0de00bf81b7ef83f77f873b240942..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmUKs7M+SzC{oH>NS%G|u)CkWs zUtb0-Ae)1Mu|1Q41;}CqVksbIU|?Fn1Q#(_z>HvnB!B$(GXp9<MGbH87;Ua7v}o_@Je%)`gw+O#tU>(>3;yN9Jr{rR2FuMs~ha-OU|;LfyH+d}I@ zzP7>n&9}eb&At8XPkQ$c!>Zp-AJjP~1^k{LcQBeE;HA3)Y(9dn*x!dfNzE-QjX@8A}_JfPxHwMJm zuTSP;oVf1NTK2xjKm6{zKOuJV6Sv#!T6f!Hg)sundMCOVBOEWO3iz$KG)GJ!!^^?8 z)qqX;R@Ql=iGoLcx2uI6nH6$UjiIl8?seNsE2nM|vx&KGx^nua&?>g7P5Z?s$?#b6 zv9D41c7ktp!Nb;|{#0#&hM(W$m7j2UN=*)0;L*Qs!Gw^lKjgOi_h??39sf#hMdh@O zO%vWQJ}P?WWqYX5WO@9@N}GRKlKTt~nsK)L_Q>puW(@XMw9w;9Oa4;3dh?`Ror_;s z-AfUEaL!3CF>2cX+7oW?xPJ9+4sdTe5u^T{QL0%(R~sm7@%WCN@v;ePPQ1N$%1?3i zY3uV|C*>TNmOa1XEYrTWZr0a^txZ9H(?lk*CDi7tB)DE!Wv>My|8x50t6$Kv`F8>o NtDdfYF6*2UngE2g^!xw- diff --git a/Telegram/Resources/icons/calls/call_settings@3x.png b/Telegram/Resources/icons/calls/call_settings@3x.png deleted file mode 100644 index d86c793a0bfa9cfe8a5a5d6b21c54e3d50003a71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 932 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvI6-QsS%!O zzP=1vKsE;hV|yk83y{SK#8N=az`(SC2`=KXfEmFCNpAj`a+QIBS=`gbF{Fa=?X18e zCPkjMjr{i<->;tM{C?Ry$M~G2+p@db{x*9@WE?)@7kD?ihYhK7PMea=-qv`2Lmnsr*5U*Ik*|(4oAvX4;L&$d$K}ydvv2&1Pmg zeM?n=@8aV_?tvGUYE~xx1xA@~BT|PmA>e!3iJTMHRZd4*y?R z@q0nK4acuN_un(83r@Tv^wu|BgmcOZk)Pk|{M_;`b%75LgJ?(Nv$ltpkG1yiwpnn@c*1GsEKBChq5zf4XSH~Y4u*=IFA<<1rJuJPFT>Zoy7!s`k*K?bGCthW1R zh1z-Sie9K(w@PF6U*;%gb=5)|aSD4M9;(Bt926InB*=L}`FDbn*oRXu`SgMNpaO&9UZph#HnLJ*PC_=Zrpg})H(Tt#8l154UhN*yf$r`AnMsw z<9h0No6A}2&lT_Y7@u$dt@qB>wDjD}`|sz>EIwz`&&41Rw_oj5eD@#o4M|K>y;u(> z2w2VKTN9>TU?KBGr);;ZSU2mI+qoZp)?8V?WP*@qnATLMHD}U{MLJyqOr%_0(w{zl z?0o2d*Ux1$L_e-IoP9PxYii2F>#swv`OoQ6HIm@zQu)~Cw_JGl3g3K(tasmkC+>{l z`s$}PS>(&yNvEGOOy}u4uAE^awc=`)jOz;dm5;S&`lzjl(c|uZvHr;GubXTu&344C zKV5Y0`Q{5TH7e6oew>&$CuDV~*l(4*?a`JxRi=tw+wbL=U1F&?cd^3e*(4JwUNMc; z=CghKoKHP3+a394|LXR`iF|WTrbH}Q<7cpDM~oid@!pq@KR(!1Zy{q5eNG@*Z+bV2 zT1riQz5C*e9kx7LV%>K#4T6(|d9?55iAU;)iG5e$ZFd$^f3f^Br)002*qN7VlQ{)@ zB0SctKYT&x?f2@FSGPpzhG>aic>Q&f?W4o*nK;~ZKL4Ck%z5*JwwQd&t(2C_FHJsG z*c?*Ybtytv==X&X3sFyn6VIQkPspEeHjVkuQXS7^Th49Ip^ei9$2eR^azlXZhopK>u+?v2MQL9>#tIgYs0#Vt>$w0b7gIf(h%TKXgbQ6DtP fF4bQvS?U@3!p`m5+|@S$l;S*H{an^LB{Ts5FAY23 literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/calls_settings@2x.png b/Telegram/Resources/icons/calls/calls_settings@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..57fd5eb9713d1ee4855ba044d5743cea2d5b4847 GIT binary patch literal 1501 zcmV<31tR*1P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NHM@d9MRA>e5Sy?D`UliYDrjV(S zNJ^BV43QUeUL+n+9wZ*5#DgM{#FJN35sC6(h>$swBr+8u$q*h$=9&H9>*U<-arQpv z?CXAgfAy`popaV+>$lf$k8AI1f)K*w_iM%U*GB#o_&)-Fy9a;N1GcudC?_Wejg5_= z>+5UO+uMs=U0s=+oSmIfUtb@(y1GImBO@p)D+`&Mn=|dT7QiIt`jJX;Nl6L5xVXUI zrK6*RiPhB9^xYgE9UbB9>}+geV#4%OiH|RM0Gpkil?5UyK0ZEhZf>qDn7q6^{Q3DQ zn#rD(m6b9dmB0milP@Fk&?Zh!PU!OTQq_22VFAs|%plUTC@wAzMMOj>`fP7+qrkvG zMH^Nj7jsinQ+$4YE^EP`DjFLbx#A#X1bgmDOojp4-Q8t1hQa!cjSYi*xd}imK0ZDM zRi@jSRtXRg?b>ZRRKOHSp|E;YpY++%+6$@bn_S#n=((;%LA8G)`P&PL=V_2ej zVxq1>u(>oeGzddwbaWKm-`}I);9x}7Yh-6<2c-X)ke)#O{r!mS4~X=(@SB^PL&e3# zh%C|6dS#4@ii(WUphm?=PfwS{N!Van4?yc59v&ihcXujVHRba1vXCyoK5%+^DwunC zc%Z1LD3q3#hC)L_)gY+li;IgWGBT1{CSjJ6l44NH?eFj7rzf0|k)ebs?E#ShJjch!nb>fc`N1JX z7Z7034n|_dWo2bLTm9I?cg19GX=#br(FOJjQVG3~Lkk#y#gd_+Ay$!~S0n(bGfUtM zY%ICCxnV+)NzN>T0!0F0VPQ;XrNcYZiBaI<;=(9FLN3HC{3@W1v6z?`#WU>P-JPN7 zucSUmp#h5}dwYA>&CN~XL*mx|mV97KOG{i+Q-dEK9$1(EXc?)^KlcZyKe_9Zoot-pHDFN8^c;)!`7?Tql4VJE9BaRy7S9UWU#d6=jWrAmKJ#B z7LvQ5$;n9sCo<9yf7X8v&6qY$$(1AZ_;d_ZNfWdGZl})*$ypM(5Dl+R78O&Q>v*qs^H2_jk0p zxk)Sfk&c{?4f5wE0JXqhOU4#M@1e|i10j7caKHrx1$bg&0+VAQ9vmE0yyPN(v*PaV zZVdT7{F|AYn)>5I5jB+{0sa~>XchWDvHM5hZ}-N3hQNgy@JDaQ00000NkvXXu0mjf DNT#ua literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/calls_settings@3x.png b/Telegram/Resources/icons/calls/calls_settings@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c9f788eab991b5209e08af7a4ea012b4422f5ebd GIT binary patch literal 2200 zcmV;J2xs?+P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS@0!c(cRCodHTxWh=>6L{ty*W6hT1{6BvG-qL?rsm?H+vIfFUXs~Vic z+wJM@nyGmw?`^>{d)*bk?(OMNeR%+Y^vO{K0;M9#bH%?A$N}W&3ORss067qFROfn7 zNdr`|VnrA|dNgd^x)ol$cmbC#U4jV{CP1lD{%w<&mj@FkPK5L4&%=`^PhiWIEii1@ zFeqELtRp;~YJSbDe;PGvgsWDq!guf9p^26*UFvOf?%cU175M!5bDT404pyyN)mvAL zSardvT)8qXTeb|peEDJwDlGi``7<_X(7+j3ty;D4{rmT!nDI$H6RC$`>C&a0`brca z4*0=?2gV>wg~yH^Lk2X;=_*yK;PKWZ@AQLfj6xVuZ~?Af!}uV25c zzy=N+h!-zj6si09^CvcK+B8d-2MCceWk^_!+P7~n*43p;7dUk25IlVN5N_PK0X!hH zo`ww@igoe8-=<9)xPSk?Sk@*Gi5>}w>SoQFC2DbP<~MKNV1)`5L_WJ>^ncgdApTv) zJ)tD&=+UDXoWY+EG&fEw{Hil@eUn2K(AiCpiCLn`KH;C z7^OU6JaXg+yng)}sDU9rKObme8)%|s4$y4fYg%AMbJ3zjv0L(Q7o0hBrZsj#(%ysU zClyTFc~2)z;+E!p9{w=#GJ`*~u1eEvus_8%T$9U46r=WGGSqH^y${cB`-Xg zhk4T?+RTfLpIV3Oq6cYeS#`gzUcHJ#h755zl~vC&EUQfi4jjm`m4S&FpsroJYWsrg z)~!Pt&|MBi*0b!|wQE)V`ug>&bx$g?2Q65zK+Ta~zkcDADN`Z?&uB3xAM0YBS~Pg@ zV4F>I52BT^mVKNzZ=TJ8SaqyZi+Jzc(nw~2-oAaSVt?1JU0D-0i!(Myi`DTYaOcjQ zDn4Gne!ZCP4FAjkty!}svGETdK4AU&^=-xz)v<22k(5rHIAQa{3{a&?m2k6570ZGnOk?&eygZAO@*(=gtxzf*Ekw3)6n` zY}=QvT)BdDWbF$MdhaOG85P{UdzWJMBYHFVF)DCLY^_+a0_Z2{ZPgnf)_CX69dE_q z!qZzI0i(ng^&pWY5kR~jKv|CoJmDu8BF3ty# zkoA?v6Ga;Necz%*d^~R=C;pX>bdJXB%t3<&`T7xNq-c8%c=I(J0-<=BF7To{lFa&LK=Q ze~@*vjij`9@7_p&A_wWVZQDx5F(Yridi7x8!iB~nhsGGJ)Ay$8XiA>3{Rgv4UAAiI?z=H=-$0MRIgq=9mAw`)TmLgefxH&erp%w zR@|^*gX%$D*n%bbJbd`DHEu#JsCV|D5MIjK0-Bb?wQJYl-o1NJzI=J${GjpU$3ve! zeL}Xs=1G@mfDQzKfB;&11qtSyL)oocH@JE8CQP0@IVksW*b7NgWaQ%w{YjH1#a<7k z(Tn!)-;aFTD9UGHxjAs55~QA#Myz6;moHzIoLKmPr~?oK$}Lxt#0d?w*W(kkghvmc zp+kq3BtAi+m9*!(_#+pqBq%28-eMU|fi&!^0wg(a(gY^dB{_8oWn52HfHby9Tn^jn z`CvhtR1=&z?>H{e&R#G-p(O=0Z{8f@Tuh;41fZ-GU|pId8PTo0BSwr657K-klM^)m z%!=ploE8@+Y14EYfWWf)?Wj*AT+A?Uk?{6`FYVZ|!@5}H3o>-i-d7#ox^=6VKaRkC zdG!EdhqwiK?Zy{uZAAYh!0Q@!&z?PPYJ$3N-@f7M)vJwnNfbcR1t@fg)@expAptqf z)Dz)q`mjj{t^RTG;>AcOtKPao{{+u;016$bQKJTq9XmF-F7@o$v*2eCCnb2~ z$dP#N+&QGhGOk>?GPp`kx17@iCeZD@nx}yOm!vum`Y$4w1IW>;Ie>BiIS_DE=X#I> apnm{BLalrect(); - p.fillRect(clip, _st.item.button.textBg); + if (_mode != Mode::Custom) { + p.fillRect(clip, _st.item.button.textBg); + } const auto repaintByStatusAfter = _repaintByStatus.remainingTime(); auto repaintAfterMin = repaintByStatusAfter; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index f4e7390e3..7588e7387 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -736,7 +736,7 @@ groupCallMemberRaisedHand: icon {{ "calls/group_calls_raised_hand", groupCallMem groupCallSettingsInner: IconButton(callButton) { iconPosition: point(-1px, 22px); - icon: icon {{ "calls/call_settings", groupCallIconFg }}; + icon: icon {{ "calls/calls_settings", groupCallIconFg }}; ripple: RippleAnimation(defaultRippleAnimation) { color: callMuteRipple; } @@ -1022,3 +1022,5 @@ desktopCaptureSourceSkip: 12px; groupCallNarrowSkip: 9px; groupCallNarrowRowSkip: 8px; groupCallNarrowSize: size(90px, 90px); +groupCallWideModeWidthMin: 800px; +groupCallWideModeSize: size(960px, 520px); diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 8f80c38b1..9a304d236 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -161,8 +161,12 @@ Call::Call( , _user(user) , _api(&_user->session().mtp()) , _type(type) -, _videoIncoming(std::make_unique(StartVideoState(video))) -, _videoOutgoing(std::make_unique(StartVideoState(video))) { +, _videoIncoming( + std::make_unique( + StartVideoState(video))) +, _videoOutgoing( + std::make_unique( + StartVideoState(video))) { _discardByTimeoutTimer.setCallback([=] { hangup(); }); if (_type == Type::Outgoing) { diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index c61015a8d..fec90fc4a 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -53,6 +53,11 @@ struct Error { QString details; }; +enum class CallType { + Incoming, + Outgoing, +}; + class Call : public base::has_weak_ptr { public: class Delegate { @@ -81,11 +86,12 @@ public: static constexpr auto kSoundSampleMs = 100; - enum class Type { - Incoming, - Outgoing, - }; - Call(not_null delegate, not_null user, Type type, bool video); + using Type = CallType; + Call( + not_null delegate, + not_null user, + Type type, + bool video); [[nodiscard]] Type type() const { return _type; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 9832b77a1..3722dad09 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -7,7 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "calls/calls_instance.h" +#include "calls/calls_call.h" #include "calls/group/calls_group_common.h" +#include "calls/group/calls_choose_join_as.h" +#include "calls/group/calls_group_call.h" #include "mtproto/mtproto_dh_utils.h" #include "core/application.h" #include "main/main_session.h" @@ -41,9 +44,128 @@ namespace { constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * crl::time(1000); +using CallSound = Call::Delegate::CallSound; +using GroupCallSound = GroupCall::Delegate::GroupCallSound; + } // namespace -Instance::Instance() = default; +class Instance::Delegate final + : public Call::Delegate + , public GroupCall::Delegate { +public: + explicit Delegate(not_null instance); + + DhConfig getDhConfig() const override; + + void callFinished(not_null call) override; + void callFailed(not_null call) override; + void callRedial(not_null call) override; + void callRequestPermissionsOrFail( + Fn onSuccess, + bool video) override; + void callPlaySound(CallSound sound) override; + auto callGetVideoCapture() + -> std::shared_ptr override; + + void groupCallFinished(not_null call) override; + void groupCallFailed(not_null call) override; + void groupCallRequestPermissionsOrFail(Fn onSuccess) override; + void groupCallPlaySound(GroupCallSound sound) override; + auto groupCallGetVideoCapture(const QString &deviceId) + -> std::shared_ptr override; + +private: + const not_null _instance; + +}; + +Instance::Delegate::Delegate(not_null instance) +: _instance(instance) { +} + +DhConfig Instance::Delegate::getDhConfig() const { + return *_instance->_cachedDhConfig; +} + +void Instance::Delegate::callFinished(not_null call) { + crl::on_main(call, [=] { + _instance->destroyCall(call); + }); +} + +void Instance::Delegate::callFailed(not_null call) { + crl::on_main(call, [=] { + _instance->destroyCall(call); + }); +} + +void Instance::Delegate::callRedial(not_null call) { + if (_instance->_currentCall.get() == call) { + _instance->refreshDhConfig(); + } +} + +void Instance::Delegate::callRequestPermissionsOrFail( + Fn onSuccess, + bool video) { + _instance->requestPermissionsOrFail(std::move(onSuccess), video); +} + +void Instance::Delegate::callPlaySound(CallSound sound) { + _instance->playSoundOnce([&] { + switch (sound) { + case CallSound::Busy: return "call_busy"; + case CallSound::Ended: return "call_end"; + case CallSound::Connecting: return "call_connect"; + } + Unexpected("CallSound in Instance::callPlaySound."); + }()); +} + +auto Instance::Delegate::callGetVideoCapture() +-> std::shared_ptr { + return _instance->getVideoCapture(); +} + +void Instance::Delegate::groupCallFinished(not_null call) { + crl::on_main(call, [=] { + _instance->destroyGroupCall(call); + }); +} + +void Instance::Delegate::groupCallFailed(not_null call) { + crl::on_main(call, [=] { + _instance->destroyGroupCall(call); + }); +} + +void Instance::Delegate::groupCallRequestPermissionsOrFail( + Fn onSuccess) { + _instance->requestPermissionsOrFail(std::move(onSuccess), false); +} + +void Instance::Delegate::groupCallPlaySound(GroupCallSound sound) { + _instance->playSoundOnce([&] { + switch (sound) { + case GroupCallSound::Started: return "group_call_start"; + case GroupCallSound::Ended: return "group_call_end"; + case GroupCallSound::AllowedToSpeak: return "group_call_allowed"; + case GroupCallSound::Connecting: return "group_call_connect"; + } + Unexpected("GroupCallSound in Instance::groupCallPlaySound."); + }()); +} + +auto Instance::Delegate::groupCallGetVideoCapture(const QString &deviceId) +-> std::shared_ptr { + return _instance->getVideoCapture(deviceId); +} + +Instance::Instance() +: _delegate(std::make_unique(this)) +, _cachedDhConfig(std::make_unique()) +, _chooseJoinAs(std::make_unique()) { +} Instance::~Instance() = default; @@ -72,7 +194,7 @@ void Instance::startOrJoinGroupCall( : peer->groupCall() ? Group::ChooseJoinAsProcess::Context::Join : Group::ChooseJoinAsProcess::Context::Create; - _chooseJoinAs.start(peer, context, [=](object_ptr box) { + _chooseJoinAs->start(peer, context, [=](object_ptr box) { Ui::show(std::move(box), Ui::LayerOption::KeepOther); }, [=](QString text) { Ui::Toast::Show(text); @@ -85,36 +207,6 @@ void Instance::startOrJoinGroupCall( }); } -void Instance::callFinished(not_null call) { - crl::on_main(call, [=] { - destroyCall(call); - }); -} - -void Instance::callFailed(not_null call) { - crl::on_main(call, [=] { - destroyCall(call); - }); -} - -void Instance::callRedial(not_null call) { - if (_currentCall.get() == call) { - refreshDhConfig(); - } -} - -void Instance::groupCallFinished(not_null call) { - crl::on_main(call, [=] { - destroyGroupCall(call); - }); -} - -void Instance::groupCallFailed(not_null call) { - crl::on_main(call, [=] { - destroyGroupCall(call); - }); -} - not_null Instance::ensureSoundLoaded( const QString &key) { const auto i = _tracks.find(key); @@ -132,31 +224,6 @@ void Instance::playSoundOnce(const QString &key) { ensureSoundLoaded(key)->playOnce(); } -void Instance::callPlaySound(CallSound sound) { - playSoundOnce([&] { - switch (sound) { - case CallSound::Busy: return "call_busy"; - case CallSound::Ended: return "call_end"; - case CallSound::Connecting: return "call_connect"; - } - Unexpected("CallSound in Instance::callPlaySound."); - return ""; - }()); -} - -void Instance::groupCallPlaySound(GroupCallSound sound) { - playSoundOnce([&] { - switch (sound) { - case GroupCallSound::Started: return "group_call_start"; - case GroupCallSound::Ended: return "group_call_end"; - case GroupCallSound::AllowedToSpeak: return "group_call_allowed"; - case GroupCallSound::Connecting: return "group_call_connect"; - } - Unexpected("GroupCallSound in Instance::groupCallPlaySound."); - return ""; - }()); -} - void Instance::destroyCall(not_null call) { if (_currentCall.get() == call) { _currentCallPanel->closeBeforeDestroy(); @@ -174,7 +241,7 @@ void Instance::destroyCall(not_null call) { } void Instance::createCall(not_null user, Call::Type type, bool video) { - auto call = std::make_unique(getCallDelegate(), user, type, video); + auto call = std::make_unique(_delegate.get(), user, type, video); const auto raw = call.get(); user->session().account().sessionChanges( @@ -217,7 +284,7 @@ void Instance::createGroupCall( destroyCurrentCall(); auto call = std::make_unique( - getGroupCallDelegate(), + _delegate.get(), std::move(info), inputCall); const auto raw = call.get(); @@ -237,7 +304,7 @@ void Instance::refreshDhConfig() { const auto weak = base::make_weak(_currentCall); _currentCall->user()->session().api().request(MTPmessages_GetDhConfig( - MTP_int(_dhConfig.version), + MTP_int(_cachedDhConfig->version), MTP_int(MTP::ModExpFirst::kRandomPowerSize) )).done([=](const MTPmessages_DhConfig &result) { const auto call = weak.get(); @@ -249,14 +316,14 @@ void Instance::refreshDhConfig() { Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize); call->start(random); } else { - callFailed(call); + _delegate->callFailed(call); } }).fail([=](const MTP::Error &error) { const auto call = weak.get(); if (!call) { return; } - callFailed(call); + _delegate->callFailed(call); }).send(); } @@ -277,13 +344,13 @@ bytes::const_span Instance::updateDhConfig( } else if (!validRandom(data.vrandom().v)) { return {}; } - _dhConfig.g = data.vg().v; - _dhConfig.p = std::move(primeBytes); - _dhConfig.version = data.vversion().v; + _cachedDhConfig->g = data.vg().v; + _cachedDhConfig->p = std::move(primeBytes); + _cachedDhConfig->version = data.vversion().v; return bytes::make_span(data.vrandom().v); }, [&](const MTPDmessages_dhConfigNotModified &data) -> bytes::const_span { - if (!_dhConfig.g || _dhConfig.p.empty()) { + if (!_cachedDhConfig->g || _cachedDhConfig->p.empty()) { LOG(("API Error: dhConfigNotModified on zero version.")); return {}; } else if (!validRandom(data.vrandom().v)) { @@ -592,22 +659,19 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn } } -std::shared_ptr Instance::callGetVideoCapture() { - return getVideoCapture(); -} - -std::shared_ptr Instance::groupCallGetVideoCapture() { - return getVideoCapture(); -} - -std::shared_ptr Instance::getVideoCapture() { +std::shared_ptr Instance::getVideoCapture( + QString deviceId) { + if (deviceId.isEmpty()) { + deviceId = Core::App().settings().callVideoInputDeviceId(); + } if (auto result = _videoCapture.lock()) { + result->switchToDevice(deviceId.toStdString()); return result; } auto result = std::shared_ptr( tgcalls::VideoCaptureInterface::Create( tgcalls::StaticThreads::getThreads(), - Core::App().settings().callVideoInputDeviceId().toStdString())); + deviceId.toStdString())); _videoCapture = result; return result; } diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 1c508c1d8..8b9c3536f 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -8,9 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "mtproto/sender.h" -#include "calls/calls_call.h" -#include "calls/group/calls_group_call.h" -#include "calls/group/calls_choose_join_as.h" namespace Platform { enum class PermissionType; @@ -27,17 +24,22 @@ class Session; namespace Calls::Group { struct JoinInfo; class Panel; +class ChooseJoinAsProcess; } // namespace Calls::Group +namespace tgcalls { +class VideoCaptureInterface; +} // namespace tgcalls + namespace Calls { +class Call; +enum class CallType; +class GroupCall; class Panel; +struct DhConfig; -class Instance - : private Call::Delegate - , private GroupCall::Delegate - , private base::Subscriber - , public base::has_weak_ptr { +class Instance : private base::Subscriber, public base::has_weak_ptr { public: Instance(); ~Instance(); @@ -69,7 +71,8 @@ public: bool activateCurrentCall(const QString &joinHash = QString()); bool minimizeCurrentActiveCall(); bool closeCurrentActiveCall(); - std::shared_ptr getVideoCapture(); + [[nodiscard]] auto getVideoCapture(QString deviceId = QString()) + -> std::shared_ptr; void requestPermissionsOrFail(Fn onSuccess, bool video = true); void setCurrentAudioDevice(bool input, const QString &deviceId); @@ -77,44 +80,13 @@ public: [[nodiscard]] bool isQuitPrevent(); private: - using CallSound = Call::Delegate::CallSound; - using GroupCallSound = GroupCall::Delegate::GroupCallSound; - - [[nodiscard]] not_null getCallDelegate() { - return static_cast(this); - } - [[nodiscard]] not_null getGroupCallDelegate() { - return static_cast(this); - } - [[nodiscard]] DhConfig getDhConfig() const override { - return _dhConfig; - } + class Delegate; + friend class Delegate; not_null ensureSoundLoaded(const QString &key); void playSoundOnce(const QString &key); - void callFinished(not_null call) override; - void callFailed(not_null call) override; - void callRedial(not_null call) override; - void callRequestPermissionsOrFail( - Fn onSuccess, - bool video) override { - requestPermissionsOrFail(std::move(onSuccess), video); - } - void callPlaySound(CallSound sound) override; - auto callGetVideoCapture() - ->std::shared_ptr override; - - void groupCallFinished(not_null call) override; - void groupCallFailed(not_null call) override; - void groupCallRequestPermissionsOrFail(Fn onSuccess) override { - requestPermissionsOrFail(std::move(onSuccess), false); - } - void groupCallPlaySound(GroupCallSound sound) override; - auto groupCallGetVideoCapture() - ->std::shared_ptr override; - - void createCall(not_null user, Call::Type type, bool video); + void createCall(not_null user, CallType type, bool video); void destroyCall(not_null call); void createGroupCall( @@ -141,7 +113,8 @@ private: not_null session, const MTPUpdate &update); - DhConfig _dhConfig; + const std::unique_ptr _delegate; + const std::unique_ptr _cachedDhConfig; crl::time _lastServerConfigUpdateTime = 0; base::weak_ptr _serverConfigRequestSession; @@ -157,7 +130,7 @@ private: base::flat_map> _tracks; - Group::ChooseJoinAsProcess _chooseJoinAs; + const std::unique_ptr _chooseJoinAs; }; diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index f0725ecdf..8675e3b92 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_call.h" #include "calls/calls_instance.h" #include "calls/calls_signal_bars.h" +#include "calls/group/calls_group_call.h" #include "calls/group/calls_group_menu.h" // Group::LeaveBox. #include "history/view/history_view_group_call_tracker.h" // ContentByCall. #include "data/data_user.h" diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 3f076471e..cc0b305b3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -313,8 +313,8 @@ GroupCall::GroupCall( , _joinHash(info.joinHash) , _id(inputCall.c_inputGroupCall().vid().v) , _scheduleDate(info.scheduleDate) -, _videoOutgoing(std::make_unique( - Webrtc::VideoState::Inactive)) +, _videoOutgoing( + std::make_unique(Webrtc::VideoState::Inactive)) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) , _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) @@ -402,6 +402,7 @@ void GroupCall::switchToCamera() { return; } _videoDeviceId = _videoInputId; + if (_videoOutgoing) _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } @@ -1366,10 +1367,42 @@ void GroupCall::setupMediaDevices() { } void GroupCall::setupOutgoingVideo() { - _videoCapture = _delegate->groupCallGetVideoCapture(); - _videoOutgoing->setState(Webrtc::VideoState::Active); - _videoCapture->setOutput(_videoOutgoing->sink()); _videoDeviceId = _videoInputId; + static const auto hasDevices = [] { + return !Webrtc::GetVideoInputList().empty(); + }; + const auto started = _videoOutgoing->state(); + if (!hasDevices()) { + _videoOutgoing->setState(Webrtc::VideoState::Inactive); + } + _videoOutgoing->stateValue( + ) | rpl::start_with_next([=](Webrtc::VideoState state) { + if (state != Webrtc::VideoState::Inactive && !hasDevices()) { + //_errors.fire({ ErrorType::NoCamera }); // #TODO videochats + _videoOutgoing->setState(Webrtc::VideoState::Inactive); + //} else if (state != Webrtc::VideoState::Inactive + // && _instance + // && !_instance->supportsVideo()) { + // _errors.fire({ ErrorType::NotVideoCall }); + // _videoOutgoing->setState(Webrtc::VideoState::Inactive); + } else if (state != Webrtc::VideoState::Inactive) { + // Paused not supported right now. + Assert(state == Webrtc::VideoState::Active); + if (!_videoCapture) { + _videoCapture = _delegate->groupCallGetVideoCapture( + _videoDeviceId); + _videoCapture->setOutput(_videoOutgoing->sink()); + } else { + _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + } + if (_instance) { + _instance->setVideoCapture(_videoCapture, nullptr); + } + _videoCapture->setState(tgcalls::VideoState::Active); + } else if (_videoCapture) { + _videoCapture->setState(tgcalls::VideoState::Inactive); + } + }, _lifetime); } void GroupCall::changeTitle(const QString &title) { @@ -1424,6 +1457,7 @@ void GroupCall::ensureControllerCreated() { const auto weak = base::make_weak(this); const auto myLevel = std::make_shared(); + _videoCall = true; tgcalls::GroupInstanceDescriptor descriptor = { .threads = tgcalls::StaticThreads::getThreads(), .config = tgcalls::GroupConfig{ @@ -1451,13 +1485,10 @@ void GroupCall::ensureControllerCreated() { .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( settings.callAudioBackend()), .videoCapture = _videoCapture, - //.getVideoSource = [=] { - // return _videoCapture-> - //}, .incomingVideoSourcesUpdated = [=]( const std::vector &ssrcs) { crl::on_main(weak, [=] { - showVideoStreams(ssrcs); + setVideoStreams(ssrcs); }); }, .participantDescriptionsRequired = [=]( @@ -1609,9 +1640,53 @@ void GroupCall::requestParticipantsInformation( addPreparedParticipants(); } -void GroupCall::showVideoStreams(const std::vector &ssrcs) { +void GroupCall::setVideoStreams(const std::vector &ssrcs) { + const auto large = _videoStreamLarge.current(); + auto newLarge = large; + if (large && !ranges::contains(ssrcs, large)) { + newLarge = 0; + _videoStreamPinned = 0; + } + auto lastSpokeVoice = crl::time(0); + auto lastSpokeVoiceSsrc = uint32(0); + auto lastSpokeAnything = crl::time(0); + auto lastSpokeAnythingSsrc = uint32(0); + auto removed = _videoStreamSsrcs; for (const auto ssrc : ssrcs) { - _videoStreamUpdated.fire_copy(ssrc); + const auto i = removed.find(ssrc); + if (i != end(removed)) { + removed.erase(i); + } else { + _videoStreamSsrcs.emplace(ssrc); + _streamsVideoUpdated.fire({ ssrc, true }); + } + if (!newLarge) { + const auto j = _lastSpoke.find(ssrc); + if (j != end(_lastSpoke)) { + if (!lastSpokeVoiceSsrc + || lastSpokeVoice < j->second.voice) { + lastSpokeVoiceSsrc = ssrc; + lastSpokeVoice = j->second.voice; + } + if (!lastSpokeAnythingSsrc + || lastSpokeAnything < j->second.anything) { + lastSpokeAnythingSsrc = ssrc; + lastSpokeAnything = j->second.anything; + } + } + } + } + if (!newLarge) { + _videoStreamLarge = lastSpokeVoiceSsrc + ? lastSpokeVoiceSsrc + : lastSpokeAnythingSsrc + ? lastSpokeAnythingSsrc + : ssrcs.empty() + ? 0 + : ssrcs.front(); + } + for (const auto ssrc : removed) { + _streamsVideoUpdated.fire({ ssrc, false }); } } @@ -2035,45 +2110,6 @@ auto GroupCall::otherParticipantStateValue() const return _otherParticipantStateValue.events(); } -//void GroupCall::setAudioVolume(bool input, float level) { -// if (_instance) { -// if (input) { -// _instance->setInputVolume(level); -// } else { -// _instance->setOutputVolume(level); -// } -// } -//} - -void GroupCall::setAudioDuckingEnabled(bool enabled) { - if (_instance) { - //_instance->setAudioOutputDuckingEnabled(enabled); - } -} - -void GroupCall::handleRequestError(const MTP::Error &error) { - //if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) { - // Ui::show(Box(tr::lng_call_error_not_available(tr::now, lt_user, _user->name))); - //} else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) { - // Ui::show(Box(tr::lng_call_error_outdated(tr::now, lt_user, _user->name))); - //} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) { - // Ui::show(Box(Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name))); - //} - //finish(FinishType::Failed); -} - -void GroupCall::handleControllerError(const QString &error) { - if (error == u"ERROR_INCOMPATIBLE"_q) { - //Ui::show(Box( - // Lang::Hard::CallErrorIncompatible().replace( - // "{user}", - // _user->name))); - } else if (error == u"ERROR_AUDIO_IO"_q) { - //Ui::show(Box(tr::lng_call_error_audio_io(tr::now))); - } - //finish(FinishType::Failed); -} - MTPInputGroupCall GroupCall::inputCall() const { Expects(_id != 0); @@ -2084,9 +2120,6 @@ MTPInputGroupCall GroupCall::inputCall() const { void GroupCall::destroyController() { if (_instance) { - //_instance->stop([](tgcalls::FinalState) { - //}); - DEBUG_LOG(("Call Info: Destroying call controller..")); _instance.reset(); DEBUG_LOG(("Call Info: Call controller destroyed.")); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 0fa69f142..7c20314f0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -74,6 +74,11 @@ struct LevelUpdate { bool me = false; }; +struct StreamsVideoUpdate { + uint32 ssrc = 0; + bool streams = false; +}; + struct VideoParams; [[nodiscard]] std::shared_ptr ParseVideoParams( @@ -100,7 +105,7 @@ public: Ended, }; virtual void groupCallPlaySound(GroupCallSound sound) = 0; - virtual auto groupCallGetVideoCapture() + virtual auto groupCallGetVideoCapture(const QString &deviceId) -> std::shared_ptr = 0; }; @@ -158,6 +163,13 @@ public: return _muted.value(); } + [[nodiscard]] bool videoCall() const { + return _videoCall.current(); + } + [[nodiscard]] rpl::producer videoCallValue() const { + return _videoCall.value(); + } + [[nodiscard]] auto otherParticipantStateValue() const -> rpl::producer; @@ -194,8 +206,15 @@ public: [[nodiscard]] rpl::producer levelUpdates() const { return _levelUpdates.events(); } - [[nodiscard]] rpl::producer videoStreamUpdated() const { - return _videoStreamUpdated.events(); + [[nodiscard]] auto streamsVideoUpdates() const + -> rpl::producer { + return _streamsVideoUpdated.events(); + } + [[nodiscard]] uint32 videoStreamLarge() const { + return _videoStreamLarge.current(); + } + [[nodiscard]] rpl::producer videoStreamLargeValue() const { + return _videoStreamLarge.value(); } [[nodiscard]] rpl::producer rejoinEvents() const { return _rejoinEvents.events(); @@ -213,8 +232,6 @@ public: bool isScreenSharing() const; void switchToCamera(); void switchToScreenSharing(const QString &uniqueId); - //void setAudioVolume(bool input, float level); - void setAudioDuckingEnabled(bool enabled); void toggleMute(const Group::MuteRequest &data); void changeVolume(const Group::VolumeRequest &data); @@ -264,8 +281,6 @@ private: void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); - void handleRequestError(const MTP::Error &error); - void handleControllerError(const QString &error); void ensureControllerCreated(); void destroyController(); @@ -304,7 +319,7 @@ private: const Data::GroupCallParticipant &participant); void addPreparedParticipants(); void addPreparedParticipantsDelayed(); - void showVideoStreams(const std::vector &ssrcs); + void setVideoStreams(const std::vector &ssrcs); void editParticipant( not_null participantPeer, @@ -347,6 +362,7 @@ private: QString _joinHash; rpl::variable _muted = MuteState::Muted; + rpl::variable _videoCall = false; bool _initialMuteStateSent = false; bool _acceptFields = false; @@ -365,7 +381,10 @@ private: std::shared_ptr _videoCapture; const std::unique_ptr _videoOutgoing; rpl::event_stream _levelUpdates; - rpl::event_stream _videoStreamUpdated; + rpl::event_stream _streamsVideoUpdated; + base::flat_set _videoStreamSsrcs; + rpl::variable _videoStreamLarge = 0; + uint32 _videoStreamPinned = 0; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; rpl::event_stream<> _allowedToSpeakNotifications; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 0cfa4d9fe..56861b193 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -47,4 +47,9 @@ struct JoinInfo { TimeId scheduleDate = 0; }; +enum class PanelMode { + Default, + Wide, +}; + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index e5317d777..38ddf3e6b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -143,6 +143,7 @@ public: } [[nodiscard]] not_null createVideoTrack(); + void clearVideoTrack(); void setVideoTrack(not_null track); void addActionRipple(QPoint point, Fn updateCallback) override; @@ -352,12 +353,14 @@ private: not_null row, uint64 raiseHandRating) const; Row *findRow(not_null participantPeer) const; + Row *findRow(uint32 audioSsrc) const; void appendInvitedUsers(); void scheduleRaisedHandStatusRemove(); const not_null _call; not_null _peer; + uint32 _largeSsrc = 0; bool _prepared = false; rpl::event_stream _toggleMuteRequests; @@ -926,6 +929,13 @@ not_null Row::createVideoTrack() { return _videoTrack.get(); } +void Row::clearVideoTrack() { + _videoTrackLifetime.destroy(); + _videoTrackShown = nullptr; + _videoTrack = nullptr; + _delegate->rowUpdateRow(this); +} + void Row::setVideoTrack(not_null track) { _videoTrackLifetime.destroy(); _videoTrackShown = track; @@ -1043,17 +1053,29 @@ void MembersController::setupListChangeViewers() { } }, _lifetime); - _call->videoStreamUpdated( - ) | rpl::start_with_next([=](uint32 ssrc) { - const auto real = _call->lookupReal(); - const auto participantPeer = real - ? real->participantPeerByAudioSsrc(ssrc) - : nullptr; - const auto row = participantPeer - ? findRow(participantPeer) - : nullptr; - if (row) { - _call->addVideoOutput(ssrc, row->createVideoTrack()); + + _call->videoStreamLargeValue( + ) | rpl::filter([=](uint32 largeSsrc) { + return (_largeSsrc != largeSsrc); + }) | rpl::start_with_next([=](uint32 largeSsrc) { + if (const auto row = findRow(_largeSsrc)) { + _call->addVideoOutput(_largeSsrc, row->createVideoTrack()); + } + _largeSsrc = largeSsrc; + if (const auto row = findRow(_largeSsrc)) { + row->clearVideoTrack(); + } + }, _lifetime); + + _call->streamsVideoUpdates( + ) | rpl::start_with_next([=](StreamsVideoUpdate update) { + Assert(update.ssrc != _largeSsrc); + if (const auto row = findRow(update.ssrc)) { + if (update.streams) { + _call->addVideoOutput(update.ssrc, row->createVideoTrack()); + } else { + row->clearVideoTrack(); + } } }, _lifetime); @@ -1345,8 +1367,6 @@ void MembersController::updateRow( } if (isMe(row->peer())) { row->setVideoTrack(_call->outgoingVideoTrack()); - } else if (nowSsrc) { - _call->addVideoOutput(nowSsrc, row->createVideoTrack()); } } const auto nowNoSounding = _soundingRowBySsrc.empty(); @@ -1378,6 +1398,17 @@ Row *MembersController::findRow(not_null participantPeer) const { delegate()->peerListFindRow(participantPeer->id.value)); } +Row *MembersController::findRow(uint32 audioSsrc) const { + if (!audioSsrc) { + return nullptr; + } + const auto real = _call->lookupReal(); + const auto participantPeer = real + ? real->participantPeerByAudioSsrc(audioSsrc) + : nullptr; + return participantPeer ? findRow(participantPeer) : nullptr; +} + Main::Session &MembersController::session() const { return _call->peer()->session(); } @@ -1943,8 +1974,11 @@ int Members::desiredHeight() const { return 0; }(); const auto use = std::max(count, _list->fullRowsCount()); + const auto single = (_mode == PanelMode::Wide) + ? (st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip) + : st::groupCallMembersList.item.height; return top - + (use * st::groupCallMembersList.item.height) + + (use * single) + (use ? st::lineWidth : 0); } @@ -2008,6 +2042,16 @@ void Members::setupAddMember(not_null call) { }, lifetime()); } +void Members::setMode(PanelMode mode) { + if (_mode == mode) { + return; + } + _mode = mode; + _list->setMode((_mode == PanelMode::Wide) + ? PeerListContent::Mode::Custom + : PeerListContent::Mode::Default); +} + rpl::producer Members::fullCountValue() const { return static_cast( _listController.get())->fullCountValue(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 20064879c..6a4d5ad2c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -26,6 +26,7 @@ namespace Calls::Group { struct VolumeRequest; struct MuteRequest; +enum class PanelMode; class Members final : public Ui::RpWidget @@ -48,6 +49,8 @@ public: return _addMemberRequests.events(); } + void setMode(PanelMode mode); + private: using ListWidget = PeerListContent; @@ -76,6 +79,7 @@ private: void updateControlsGeometry(); const not_null _call; + PanelMode _mode = PanelMode(); object_ptr _scroll; std::unique_ptr _listController; object_ptr _addMember = { nullptr }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 7b7485d9f..4a1dfcb0d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -525,6 +525,11 @@ void Panel::initWindow() { ? (Flag::Move | Flag::Maximize) : Flag::None; }); + + _call->videoCallValue( + ) | rpl::start_with_next([=] { + updateMode(); + }, _window->lifetime()); } void Panel::initWidget() { @@ -536,8 +541,10 @@ void Panel::initWidget() { }, widget()->lifetime()); widget()->sizeValue( - ) | rpl::skip(1) | rpl::start_with_next([=] { - updateControlsGeometry(); + ) | rpl::skip(1) | rpl::start_with_next([=](QSize size) { + if (!updateMode()) { + updateControlsGeometry(); + } // title geometry depends on _controls->geometry, // which is not updated here yet. @@ -1384,6 +1391,21 @@ QRect Panel::computeTitleRect() const { #endif // !Q_OS_MAC } +bool Panel::updateMode() { + const auto wide = _call->videoCall() + && (widget()->width() >= st::groupCallWideModeWidthMin); + const auto mode = wide ? PanelMode::Wide : PanelMode::Default; + if (_mode == mode) { + return false; + } + _mode = mode; + if (_members) { + _members->setMode(mode); + } + updateControlsGeometry(); + return true; +} + void Panel::updateControlsGeometry() { if (widget()->size().isEmpty() || (!_settings && !_share)) { return; @@ -1434,27 +1456,35 @@ void Panel::updateMembersGeometry() { if (!_members) { return; } - const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip; - const auto membersTop = st::groupCallMembersTop; - const auto availableHeight = muteTop - - membersTop - - st::groupCallMembersMargin.bottom(); const auto desiredHeight = _members->desiredHeight(); - const auto membersWidthAvailable = widget()->width() - - st::groupCallMembersMargin.left() - - st::groupCallMembersMargin.right(); - const auto membersWidthMin = st::groupCallWidth - - st::groupCallMembersMargin.left() - - st::groupCallMembersMargin.right(); - const auto membersWidth = std::clamp( - membersWidthAvailable, - membersWidthMin, - st::groupCallMembersWidthMax); - _members->setGeometry( - (widget()->width() - membersWidth) / 2, - membersTop, - membersWidth, - std::min(desiredHeight, availableHeight)); + if (_mode == PanelMode::Wide) { + _members->setGeometry( + st::groupCallNarrowSkip, + 0, + st::groupCallNarrowSize.width(), + std::min(desiredHeight, widget()->height())); + } else { + const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip; + const auto membersTop = st::groupCallMembersTop; + const auto availableHeight = muteTop + - membersTop + - st::groupCallMembersMargin.bottom(); + const auto membersWidthAvailable = widget()->width() + - st::groupCallMembersMargin.left() + - st::groupCallMembersMargin.right(); + const auto membersWidthMin = st::groupCallWidth + - st::groupCallMembersMargin.left() + - st::groupCallMembersMargin.right(); + const auto membersWidth = std::clamp( + membersWidthAvailable, + membersWidthMin, + st::groupCallMembersWidthMax); + _members->setGeometry( + (widget()->width() - membersWidth) / 2, + membersTop, + membersWidth, + std::min(desiredHeight, availableHeight)); + } } void Panel::refreshTitle() { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 2ae20b525..51e23a5f7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -53,6 +53,7 @@ struct CallBodyLayout; namespace Calls::Group { class Members; +enum class PanelMode; class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate { public: @@ -88,6 +89,7 @@ private: bool handleClose(); void startScheduledNow(); + bool updateMode(); void updateControlsGeometry(); void updateMembersGeometry(); void showControls(); @@ -118,6 +120,7 @@ private: const std::unique_ptr _window; const std::unique_ptr _layerBg; + PanelMode _mode = PanelMode(); #ifndef Q_OS_MAC std::unique_ptr _controls; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index fd3b0f86a..6e82e6323 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -93,8 +93,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/changelogs.h" #include "base/unixtime.h" +#include "calls/calls_call.h" #include "calls/calls_instance.h" #include "calls/calls_top_bar.h" +#include "calls/group/calls_group_call.h" #include "export/export_settings.h" #include "export/export_manager.h" #include "export/view/export_view_top_bar.h" diff --git a/Telegram/SourceFiles/settings/settings_calls.cpp b/Telegram/SourceFiles/settings/settings_calls.cpp index 189f2bbc5..fe7220728 100644 --- a/Telegram/SourceFiles/settings/settings_calls.cpp +++ b/Telegram/SourceFiles/settings/settings_calls.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "core/application.h" #include "core/core_settings.h" +#include "calls/calls_call.h" #include "calls/calls_instance.h" #include "calls/calls_video_bubble.h" #include "webrtc/webrtc_media_devices.h" @@ -250,40 +251,6 @@ void Calls::setupContent() { AddSkip(content); AddSubsectionTitle(content, tr::lng_settings_call_section_other()); -//#if defined Q_OS_MAC && !defined OS_MAC_STORE -// AddButton( -// content, -// tr::lng_settings_call_audio_ducking(), -// st::settingsButton -// )->toggleOn( -// rpl::single(settings.callAudioDuckingEnabled()) -// )->toggledValue() | rpl::filter([](bool enabled) { -// return (enabled != Core::App().settings().callAudioDuckingEnabled()); -// }) | rpl::start_with_next([=](bool enabled) { -// Core::App().settings().setCallAudioDuckingEnabled(enabled); -// Core::App().saveSettingsDelayed(); -// if (const auto call = Core::App().calls().currentCall()) { -// call->setAudioDuckingEnabled(enabled); -// } -// }, content->lifetime()); -//#endif // Q_OS_MAC && !OS_MAC_STORE - - //const auto backend = [&]() -> QString { - // using namespace Webrtc; - // switch (settings.callAudioBackend()) { - // case Backend::OpenAL: return "OpenAL"; - // case Backend::ADM: return "WebRTC ADM"; - // case Backend::ADM2: return "WebRTC ADM2"; - // } - // Unexpected("Value in backend."); - //}(); - //AddButton( - // content, - // rpl::single("Call audio backend: " + backend), - // st::settingsButton - //)->addClickHandler([] { - // Ui::show(ChooseAudioBackendBox()); - //}); AddButton( content, tr::lng_settings_call_accept_calls(), diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 84c00409a..cf923bbc3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/toasts/common_toasts.h" #include "calls/calls_instance.h" // Core::App().calls().inCall(). +#include "calls/group/calls_group_call.h" #include "ui/boxes/calendar_box.h" #include "boxes/confirm_box.h" #include "mainwidget.h" From eb8f70994349fafa36b273ef0eddcfb82c1b1b0c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Apr 2021 15:07:12 +0400 Subject: [PATCH 074/477] Show members list in PanelMode::Wide. --- Telegram/SourceFiles/boxes/boxes.style | 4 - Telegram/SourceFiles/boxes/peer_list_box.cpp | 32 ++- Telegram/SourceFiles/boxes/peer_list_box.h | 18 +- .../calls/group/calls_group_members.cpp | 265 +++++++++++++----- .../chat_helpers/chat_helpers.style | 4 - .../chat_helpers/field_autocomplete.cpp | 4 +- Telegram/SourceFiles/dialogs/dialogs.style | 4 - .../SourceFiles/dialogs/dialogs_widget.cpp | 2 +- Telegram/SourceFiles/info/info.style | 5 - .../SourceFiles/info/info_content_widget.cpp | 2 +- Telegram/SourceFiles/passport/passport.style | 3 - .../SourceFiles/settings/settings_intro.cpp | 2 +- .../support/support_autocomplete.cpp | 4 +- Telegram/SourceFiles/ui/chat/chat.style | 1 - .../window/themes/window_theme_editor.cpp | 2 +- 15 files changed, 231 insertions(+), 121 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 0e299dd22..2e6ee6905 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -729,10 +729,6 @@ termsAgePadding: margins(22px, 16px, 16px, 0px); themesSmallSkip: 10px; themesBackgroundSize: 120px; -themesScroll: ScrollArea(defaultScrollArea) { - bottomsh: 0px; - topsh: 0px; -} themesMenuToggle: IconButton(defaultIconButton) { width: 44px; height: 44px; diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index 7feb3b1a3..4b5530416 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -529,7 +529,7 @@ QString PeerListRow::generateShortName() { : peer()->shortName(); } -std::shared_ptr PeerListRow::ensureUserpicView() { +std::shared_ptr &PeerListRow::ensureUserpicView() { if (!_userpic) { _userpic = peer()->createUserpicView(); } @@ -588,11 +588,14 @@ void PeerListRow::paintStatusText( _status.drawLeftElided(p, x, y, availableWidth, outerWidth); } -template -void PeerListRow::addRipple(const style::PeerListItem &st, QSize size, QPoint point, UpdateCallback updateCallback) { +template +void PeerListRow::addRipple(const style::PeerListItem &st, MaskGenerator &&maskGenerator, QPoint point, UpdateCallback &&updateCallback) { if (!_ripple) { - auto mask = Ui::RippleAnimation::rectMask(size); - _ripple = std::make_unique(st.button.ripple, std::move(mask), std::move(updateCallback)); + auto mask = maskGenerator(); + if (mask.isNull()) { + return; + } + _ripple = std::make_unique(st.button.ripple, std::move(mask), std::forward(updateCallback)); } _ripple->add(point); } @@ -1241,9 +1244,16 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) { row->addActionRipple(point, std::move(updateCallback)); } } else { - auto size = QSize(width(), _rowHeight); auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index)); - row->addRipple(_st.item, size, point, std::move(updateCallback)); + if (_mode == Mode::Custom) { + row->addRipple(_st.item, _controller->customRowRippleMaskGenerator(), point, std::move(updateCallback)); + } else { + const auto maskGenerator = [&] { + return Ui::RippleAnimation::rectMask( + QSize(width(), _rowHeight)); + }; + row->addRipple(_st.item, maskGenerator, point, std::move(updateCallback)); + } } } if (anim::Disabled()) { @@ -1779,7 +1789,11 @@ void PeerListContent::selectByMouse(QPoint globalPosition) { auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(globalPosition)); auto selected = Selected(); auto rowsPointY = point.y() - rowsTop(); - selected.index.value = (in && rowsPointY >= 0 && rowsPointY < shownRowsCount() * _rowHeight) ? (rowsPointY / _rowHeight) : -1; + selected.index.value = (in + && rowsPointY >= 0 + && rowsPointY < shownRowsCount() * _rowHeight) + ? (rowsPointY / _rowHeight) + : -1; if (selected.index.value >= 0) { const auto row = getRow(selected.index); if (row->disabled() @@ -1787,7 +1801,7 @@ void PeerListContent::selectByMouse(QPoint globalPosition) { && !_controller->customRowSelectionPoint( row, point.x(), - rowsPointY))) { + rowsPointY - (selected.index.value * _rowHeight)))) { selected = Selected(); } else if (!customMode) { if (getActiveActionRect(row, selected.index).contains(point)) { diff --git a/Telegram/SourceFiles/boxes/peer_list_box.h b/Telegram/SourceFiles/boxes/peer_list_box.h index 32b6b26e5..abce0b55b 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.h +++ b/Telegram/SourceFiles/boxes/peer_list_box.h @@ -82,7 +82,7 @@ public: return _id; } - [[nodiscard]] std::shared_ptr ensureUserpicView(); + [[nodiscard]] std::shared_ptr &ensureUserpicView(); [[nodiscard]] virtual QString generateName(); [[nodiscard]] virtual QString generateShortName(); @@ -172,12 +172,12 @@ public: void finishCheckedAnimation(); void invalidatePixmapsCache(); - template + template void addRipple( const style::PeerListItem &st, - QSize size, + MaskGenerator &&maskGenerator, QPoint point, - UpdateCallback updateCallback); + UpdateCallback &&updateCallback); void stopLastRipple(); void paintRipple(Painter &p, int x, int y, int outerWidth); void paintUserpic( @@ -451,21 +451,23 @@ public: return false; } [[nodiscard]] virtual int customRowHeight() { - Unexpected("Unimplemented PeerListController::customRowHeight."); + Unexpected("PeerListController::customRowHeight."); } virtual void customRowPaint( Painter &p, crl::time now, not_null row, bool selected) { - Unexpected("Unimplemented PeerListController::customRowPaint."); + Unexpected("PeerListController::customRowPaint."); } [[nodiscard]] virtual bool customRowSelectionPoint( not_null row, int x, int y) { - Unexpected( - "Unimplemented PeerListController::customRowSelectionPoint."); + Unexpected("PeerListController::customRowSelectionPoint."); + } + [[nodiscard]] virtual Fn customRowRippleMaskGenerator() { + Unexpected("PeerListController::customRowRippleMaskGenerator."); } [[nodiscard]] virtual rpl::producer onlineCountValue() const; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 38ddf3e6b..8c7857476 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "ui/effects/ripple_animation.h" #include "ui/effects/cross_line.h" +#include "ui/round_rect.h" #include "core/application.h" // Core::App().domain, Core::App().activeWindow. #include "main/main_domain.h" // Core::App().domain().activate. #include "main/main_session.h" @@ -97,6 +98,7 @@ public: Painter &p, QRect rect, IconState state) = 0; + virtual void rowPaintWideBackground(Painter &p, bool selected) = 0; }; class Row final : public PeerListRow { @@ -174,7 +176,15 @@ public: bool selected, bool actionSelected) override; - auto generatePaintUserpicCallback() -> PaintRoundImageCallback override; + PaintRoundImageCallback generatePaintUserpicCallback() override; + void paintComplexUserpic( + Painter &p, + int x, + int y, + int outerWidth, + int size, + PanelMode mode, + bool selected = false); void paintStatusText( Painter &p, @@ -243,6 +253,20 @@ private: void ensureUserpicCache( std::shared_ptr &view, int size); + bool paintVideo(Painter &p, int x, int y, int size, PanelMode mode); + [[nodiscard]] static std::tuple UserpicInWideMode( + int x, + int y, + int size); + void paintBlobs(Painter &p, int x, int y, int size, PanelMode mode); + void paintScaledUserpic( + Painter &p, + std::shared_ptr &userpic, + int x, + int y, + int outerWidth, + int size, + PanelMode mode); const not_null _delegate; State _state = State::Inactive; @@ -305,6 +329,7 @@ public: Painter &p, QRect rect, IconState state) override; + void rowPaintWideBackground(Painter &p, bool selected) override; int customRowHeight() override; void customRowPaint( @@ -316,6 +341,7 @@ public: not_null row, int x, int y) override; + Fn customRowRippleMaskGenerator() override; private: [[nodiscard]] std::unique_ptr createRowForMe(); @@ -383,6 +409,8 @@ private: Ui::CrossLineAnimation _inactiveCrossLine; Ui::CrossLineAnimation _coloredCrossLine; + Ui::RoundRect _wideRoundRectSelected; + Ui::RoundRect _wideRoundRect; rpl::lifetime _lifetime; @@ -653,78 +681,142 @@ void Row::ensureUserpicCache( } } -auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback { - auto userpic = ensureUserpicView(); - return [=](Painter &p, int x, int y, int outerWidth, int size) mutable { - const auto videoSize = _videoTrackShown - ? _videoTrackShown->frameSize() - : QSize(); - if (!videoSize.isEmpty()) { - const auto resize = (videoSize.width() > videoSize.height()) - ? QSize(videoSize.width() * size / videoSize.height(), size) - : QSize(size, videoSize.height() * size / videoSize.width()); - const auto request = Webrtc::FrameRequest{ - .resize = resize * cIntRetinaFactor(), - .outer = QSize(size, size) * cIntRetinaFactor(), - }; - const auto frame = _videoTrackShown->frame(request); - auto copy = frame; // #TODO calls optimize. - copy.detach(); - Images::prepareCircle(copy); - p.drawImage( - QRect(QPoint(x, y), copy.size() / cIntRetinaFactor()), - copy); - _videoTrackShown->markFrameShown(); - return; - } else if (_videoTrackShown) { - // We could skip the first notification. - _videoTrackShown->markFrameShown(); - } - if (_blobsAnimation) { - const auto mutedByMe = (_state == State::MutedByMe); - const auto shift = QPointF(x + size / 2., y + size / 2.); - auto hq = PainterHighQualityEnabler(p); - p.translate(shift); - const auto brush = mutedByMe - ? st::groupCallMemberMutedIcon->b - : anim::brush( - st::groupCallMemberInactiveStatus, - st::groupCallMemberActiveStatus, - _speakingAnimation.value(_speaking ? 1. : 0.)); - _blobsAnimation->blobs.paint(p, brush); - p.translate(-shift); - p.setOpacity(1.); - - const auto enter = _blobsAnimation->enter; - const auto &minScale = kUserpicMinScale; - const auto scaleUserpic = minScale - + (1. - minScale) * _blobsAnimation->blobs.currentLevel(); - const auto scale = scaleUserpic * enter + 1. * (1. - enter); - if (scale == 1.) { - peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size); - } else { - ensureUserpicCache(userpic, size); - - PainterHighQualityEnabler hq(p); - - auto target = QRect( - x + (1 - kWideScale) / 2 * size, - y + (1 - kWideScale) / 2 * size, - kWideScale * size, - kWideScale * size); - auto shrink = anim::interpolate( - (1 - kWideScale) / 2 * size, - 0, - scale); - auto margins = QMargins(shrink, shrink, shrink, shrink); - p.drawImage( - target.marginsAdded(margins), - _blobsAnimation->userpicCache); - } - } else { - peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size); - } +bool Row::paintVideo(Painter &p, int x, int y, int size, PanelMode mode) { + if (!_videoTrackShown) { + return false; + } + const auto guard = gsl::finally([&] { + _videoTrackShown->markFrameShown(); + }); + const auto videoSize = _videoTrackShown->frameSize(); + if (videoSize.isEmpty()) { + return false; + } + const auto resize = (videoSize.width() > videoSize.height()) + ? QSize(videoSize.width() * size / videoSize.height(), size) + : QSize(size, videoSize.height() * size / videoSize.width()); + const auto request = Webrtc::FrameRequest{ + .resize = resize * cIntRetinaFactor(), + .outer = QSize(size, size) * cIntRetinaFactor(), }; + const auto frame = _videoTrackShown->frame(request); + auto copy = frame; // #TODO calls optimize. + copy.detach(); + if (mode == PanelMode::Default) { + Images::prepareCircle(copy); + } else { + Images::prepareRound(copy, ImageRoundRadius::Large); + } + p.drawImage( + QRect(QPoint(x, y), copy.size() / cIntRetinaFactor()), + copy); + return true; +} + +std::tuple Row::UserpicInWideMode(int x, int y, int size) { + const auto useSize = st::groupCallMembersList.item.photoSize; + const auto skip = (size - useSize) / 2; + return { x + skip, y + skip, useSize }; +} + +void Row::paintBlobs(Painter &p, int x, int y, int size, PanelMode mode) { + if (!_blobsAnimation) { + return; + } + if (mode == PanelMode::Wide) { + std::tie(x, y, size) = UserpicInWideMode(x, y, size); + } + const auto mutedByMe = (_state == State::MutedByMe); + const auto shift = QPointF(x + size / 2., y + size / 2.); + auto hq = PainterHighQualityEnabler(p); + p.translate(shift); + const auto brush = mutedByMe + ? st::groupCallMemberMutedIcon->b + : anim::brush( + st::groupCallMemberInactiveStatus, + st::groupCallMemberActiveStatus, + _speakingAnimation.value(_speaking ? 1. : 0.)); + _blobsAnimation->blobs.paint(p, brush); + p.translate(-shift); + p.setOpacity(1.); +} + +void Row::paintScaledUserpic( + Painter &p, + std::shared_ptr &userpic, + int x, + int y, + int outerWidth, + int size, + PanelMode mode) { + if (mode == PanelMode::Wide) { + std::tie(x, y, size) = UserpicInWideMode(x, y, size); + } + if (!_blobsAnimation) { + peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size); + return; + } + const auto enter = _blobsAnimation->enter; + const auto &minScale = kUserpicMinScale; + const auto scaleUserpic = minScale + + (1. - minScale) * _blobsAnimation->blobs.currentLevel(); + const auto scale = scaleUserpic * enter + 1. * (1. - enter); + if (scale == 1.) { + peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size); + return; + } + ensureUserpicCache(userpic, size); + + PainterHighQualityEnabler hq(p); + + auto target = QRect( + x + (1 - kWideScale) / 2 * size, + y + (1 - kWideScale) / 2 * size, + kWideScale * size, + kWideScale * size); + auto shrink = anim::interpolate( + (1 - kWideScale) / 2 * size, + 0, + scale); + auto margins = QMargins(shrink, shrink, shrink, shrink); + p.drawImage( + target.marginsAdded(margins), + _blobsAnimation->userpicCache); +} + +auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback { + return [=](Painter &p, int x, int y, int outerWidth, int size) { + paintComplexUserpic(p, x, y, outerWidth, size, PanelMode::Default); + }; +} + +void Row::paintComplexUserpic( + Painter &p, + int x, + int y, + int outerWidth, + int size, + PanelMode mode, + bool selected) { + if (mode == PanelMode::Wide) { + if (paintVideo(p, x, y, size, mode)) { + return; + } + _delegate->rowPaintWideBackground(p, selected); + paintRipple(p, x, y, outerWidth); + } + paintBlobs(p, x, y, size, mode); + if (mode == PanelMode::Default && paintVideo(p, x, y, size, mode)) { + return; + } + paintScaledUserpic( + p, + ensureUserpicView(), + x, + y, + outerWidth, + size, + mode); } int Row::statusIconWidth() const { @@ -976,7 +1068,9 @@ MembersController::MembersController( , _menuParent(menuParent) , _raisedHandStatusRemoveTimer([=] { scheduleRaisedHandStatusRemove(); }) , _inactiveCrossLine(st::groupCallMemberInactiveCrossLine) -, _coloredCrossLine(st::groupCallMemberColoredCrossLine) { +, _coloredCrossLine(st::groupCallMemberColoredCrossLine) +, _wideRoundRectSelected(ImageRoundRadius::Large, st::groupCallMembersBgOver) +, _wideRoundRect(ImageRoundRadius::Large, st::groupCallMembersBg) { setupListChangeViewers(); style::PaletteChanged( @@ -1605,6 +1699,12 @@ void MembersController::rowPaintIcon( _inactiveCrossLine.paint(p, left, top, crossProgress, iconColor); } +void MembersController::rowPaintWideBackground(Painter &p, bool selected) { + (selected ? _wideRoundRectSelected : _wideRoundRect).paint( + p, + { QPoint(), st::groupCallNarrowSize }); +} + int MembersController::customRowHeight() { return st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip; } @@ -1615,6 +1715,15 @@ void MembersController::customRowPaint( not_null row, bool selected) { const auto real = static_cast(row.get()); + const auto width = st::groupCallNarrowSize.width(); + real->paintComplexUserpic( + p, + 0, + 0, + width, + width, + PanelMode::Wide, + selected); } bool MembersController::customRowSelectionPoint( @@ -1624,6 +1733,14 @@ bool MembersController::customRowSelectionPoint( return y < st::groupCallNarrowSize.height(); } +Fn MembersController::customRowRippleMaskGenerator() { + return [] { + return Ui::RippleAnimation::roundRectMask( + st::groupCallNarrowSize, + st::roundRadiusLarge); + }; +} + auto MembersController::kickParticipantRequests() const -> rpl::producer>{ return _kickParticipantRequests.events(); @@ -1938,7 +2055,7 @@ Members::Members( not_null call) : RpWidget(parent) , _call(call) -, _scroll(this, st::defaultSolidScroll) +, _scroll(this) , _listController(std::make_unique(call, parent)) { setupAddMember(call); setupList(); diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 25ae77ad8..58880770d 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -248,10 +248,6 @@ emojiSuggestionsPadding: margins(emojiColorsPadding, 0px, emojiColorsPadding, 0p emojiSuggestionsFadeAfter: 20px; mentionHeight: 40px; -mentionScroll: ScrollArea(defaultScrollArea) { - topsh: 0px; - bottomsh: 0px; -} mentionPadding: margins(8px, 5px, 8px, 5px); mentionTop: 11px; mentionFont: linkFont; diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 1a318aa51..30fc60d0e 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -140,7 +140,7 @@ FieldAutocomplete::FieldAutocomplete( not_null controller) : RpWidget(parent) , _controller(controller) -, _scroll(this, st::mentionScroll) { +, _scroll(this) { hide(); _scroll->setGeometry(rect()); @@ -759,7 +759,7 @@ void FieldAutocomplete::Inner::paintEvent(QPaintEvent *e) { auto htagwidth = width() - st::mentionPadding.right() - htagleft - - st::mentionScroll.width; + - st::defaultScrollArea.width; if (!_srows->empty()) { int32 rows = rowscount(_srows->size(), _stickersPerRow); diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index c12055e8d..e71c779a4 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -51,10 +51,6 @@ dialogsSkip: 8px; dialogsWidthDuration: 120; dialogsTextWidthMin: 150px; -dialogsScroll: ScrollArea(defaultScrollArea) { - topsh: 0px; - bottomsh: 0px; -} dialogsTextPalette: TextPalette(defaultTextPalette) { linkFg: dialogsTextFgService; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index a174fa90b..5641540c5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -174,7 +174,7 @@ Widget::Widget( object_ptr(this, st::dialogsCalendar)) , _cancelSearch(_searchControls, st::dialogsCancelSearch) , _lockUnlock(_searchControls, st::dialogsLock) -, _scroll(this, st::dialogsScroll) +, _scroll(this) , _scrollToTop(_scroll, st::dialogsToUp) , _singleMessageSearch(&controller->session()) { _inner = _scroll->setOwnedWidget(object_ptr(this, controller)); diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index d6634d9b9..be68973b4 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -37,11 +37,6 @@ infoToggle: InfoToggle { rippleAreaPadding: 8px; } -infoScroll: ScrollArea(defaultScrollArea) { - bottomsh: 0px; - topsh: 0px; -} - infoMediaSearch: SearchFieldRow { height: 44px; padding: margins(8px, 6px, 8px, 6px); diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index c52cb2fb8..c9789093c 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -37,7 +37,7 @@ ContentWidget::ContentWidget( not_null controller) : RpWidget(parent) , _controller(controller) -, _scroll(this, st::infoScroll) { +, _scroll(this) { using namespace rpl::mappers; setAttribute(Qt::WA_OpaquePaintEvent); diff --git a/Telegram/SourceFiles/passport/passport.style b/Telegram/SourceFiles/passport/passport.style index 81b7b1cf6..a0d422282 100644 --- a/Telegram/SourceFiles/passport/passport.style +++ b/Telegram/SourceFiles/passport/passport.style @@ -61,9 +61,6 @@ passportPasswordForgotBottom: 36px; passportPanelScroll: ScrollArea(defaultScrollArea) { deltat: 6px; deltab: 6px; - - topsh: 0px; - bottomsh: 0px; } passportPanelAuthorize: RoundButton(passportPasswordSubmit) { diff --git a/Telegram/SourceFiles/settings/settings_intro.cpp b/Telegram/SourceFiles/settings/settings_intro.cpp index 44f0ef147..ec7f1cdbb 100644 --- a/Telegram/SourceFiles/settings/settings_intro.cpp +++ b/Telegram/SourceFiles/settings/settings_intro.cpp @@ -217,7 +217,7 @@ IntroWidget::IntroWidget( not_null window) : RpWidget(parent) , _wrap(this) -, _scroll(Ui::CreateChild(_wrap.data(), st::infoScroll)) +, _scroll(Ui::CreateChild(_wrap.data())) , _topShadow(this) { _wrap->setAttribute(Qt::WA_OpaquePaintEvent); _wrap->paintRequest( diff --git a/Telegram/SourceFiles/support/support_autocomplete.cpp b/Telegram/SourceFiles/support/support_autocomplete.cpp index d601e5611..fdf7c105d 100644 --- a/Telegram/SourceFiles/support/support_autocomplete.cpp +++ b/Telegram/SourceFiles/support/support_autocomplete.cpp @@ -407,9 +407,7 @@ void Autocomplete::setupContent() { rpl::single(qsl("Search for templates"))), // #TODO hard_lang st::autocompleteSearchPadding); const auto input = inputWrap->entity(); - const auto scroll = Ui::CreateChild( - this, - st::mentionScroll); + const auto scroll = Ui::CreateChild(this); const auto inner = scroll->setOwnedWidget(object_ptr(scroll)); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index a89ff17e7..3fa8ca553 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -60,7 +60,6 @@ historyScroll: ScrollArea(defaultScrollArea) { deltat: 3px; deltab: 3px; - topsh: 0px; bottomsh: -1px; } diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp index bfd6a42c9..59a7a5ddf 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp @@ -649,7 +649,7 @@ Editor::Editor( const Data::CloudTheme &cloud) : _window(window) , _cloud(cloud) -, _scroll(this, st::themesScroll) +, _scroll(this) , _close(this, st::defaultMultiSelect.fieldCancel) , _menuToggle(this, st::themesMenuToggle) , _select(this, st::defaultMultiSelect, tr::lng_country_ph()) From b15623d4359f9889ca7c2d4c34ba6a282d3a4bf1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Apr 2021 18:47:03 +0400 Subject: [PATCH 075/477] Allow pinning video to top of members list. --- Telegram/Resources/langs/lang.strings | 7 + .../calls/group/calls_group_call.cpp | 120 +++++++++-- .../calls/group/calls_group_call.h | 17 ++ .../calls/group/calls_group_members.cpp | 188 ++++++++++++++---- .../calls/group/calls_group_members.h | 12 +- Telegram/lib_webrtc | 2 +- 6 files changed, 292 insertions(+), 54 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d8d262f69..6184a552f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1998,6 +1998,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_raised_hand_status" = "wants to speak"; "lng_group_call_settings" = "Settings"; "lng_group_call_share_button" = "Share"; +"lng_group_call_screen_share" = "Share"; +"lng_group_call_unmute_small" = "Unmute"; +"lng_group_call_you_are_live_small" = "Mute"; +"lng_group_call_force_muted_small" = "Muted"; +"lng_group_call_more" = "More"; "lng_group_call_unmute" = "Unmute"; "lng_group_call_unmute_sub" = "or hold spacebar to talk"; "lng_group_call_you_are_live" = "You are Live"; @@ -2059,6 +2064,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_context_remove_hand" = "Cancel request to speak"; "lng_group_call_context_mute_for_me" = "Mute for me"; "lng_group_call_context_unmute_for_me" = "Unmute for me"; +"lng_group_call_context_pin_video" = "Pin video"; +"lng_group_call_context_unpin_video" = "Unpin video"; "lng_group_call_context_remove" = "Remove"; "lng_group_call_remove_channel" = "Remove {channel} from the voice chat?"; "lng_group_call_duration_days#one" = "{count} day"; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index cc0b305b3..1910027c3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -111,6 +111,14 @@ private: }; +struct GroupCall::LargeTrack { + LargeTrack() : track(Webrtc::VideoState::Active) { + } + + Webrtc::VideoTrack track; + std::shared_ptr sink; +}; + [[nodiscard]] bool IsGroupCallAdmin( not_null peer, not_null participantPeer) { @@ -427,6 +435,53 @@ void GroupCall::subscribeToReal(not_null real) { ) | rpl::start_with_next([=](TimeId date) { setScheduledDate(date); }, _lifetime); + + using Update = Data::GroupCall::ParticipantUpdate; + real->participantUpdated( + ) | rpl::start_with_next([=](const Update &data) { + const auto nowSpeaking = data.now && data.now->speaking; + const auto nowSounding = data.now && data.now->sounding; + const auto wasSpeaking = data.was && data.was->speaking; + const auto wasSounding = data.was && data.was->sounding; + if (nowSpeaking == wasSpeaking && nowSounding == wasSounding) { + return; + } else if (_videoStreamPinned) { + return; + } + const auto videoLargeSsrc = _videoStreamLarge.current(); + const auto &participants = real->participants(); + if ((wasSpeaking || wasSounding) + && (data.was->ssrc == videoLargeSsrc)) { + auto bestWithVideoSsrc = uint32(0); + for (const auto &participant : participants) { + if (!participant.sounding + || !participant.ssrc + || !_videoStreamSsrcs.contains(participant.ssrc)) { + continue; + } + if (participant.speaking) { + bestWithVideoSsrc = participant.ssrc; + break; + } else if (!bestWithVideoSsrc) { + bestWithVideoSsrc = participant.ssrc; + } + } + if (bestWithVideoSsrc) { + _videoStreamLarge = bestWithVideoSsrc; + } + } else if ((nowSpeaking || nowSounding) + && (data.now->ssrc != videoLargeSsrc)) { + const auto i = ranges::find( + participants, + videoLargeSsrc, + &Data::GroupCallParticipant::ssrc); + const auto speaking = (i != end(participants)) && i->speaking; + const auto sounding = (i != end(participants)) && i->sounding; + if ((nowSpeaking && !speaking) || (nowSounding && !sounding)) { + _videoStreamLarge = data.now->ssrc; + } + } + }, _lifetime); } void GroupCall::checkGlobalShortcutAvailability() { @@ -1531,6 +1586,22 @@ void GroupCall::ensureControllerCreated() { LOG(("Call Info: Creating group instance")); _instance = std::make_unique( std::move(descriptor)); + _videoStreamLarge.changes( + ) | rpl::start_with_next([=](uint32 ssrc) { + _instance->setFullSizeVideoSsrc(ssrc); + if (!ssrc) { + _videoLargeTrack = nullptr; + _videoLargeTrackWrap = nullptr; + return; + } + if (!_videoLargeTrackWrap) { + _videoLargeTrackWrap = std::make_unique(); + _videoLargeTrack = &_videoLargeTrackWrap->track; + } + _videoLargeTrackWrap->sink = Webrtc::CreateProxySink( + _videoLargeTrackWrap->track.sink()); + _instance->addIncomingVideoOutput(ssrc, _videoLargeTrackWrap->sink); + }, _lifetime); updateInstanceMuteState(); updateInstanceVolumes(); @@ -1641,15 +1712,14 @@ void GroupCall::requestParticipantsInformation( } void GroupCall::setVideoStreams(const std::vector &ssrcs) { + const auto real = lookupReal(); const auto large = _videoStreamLarge.current(); auto newLarge = large; if (large && !ranges::contains(ssrcs, large)) { newLarge = 0; _videoStreamPinned = 0; } - auto lastSpokeVoice = crl::time(0); auto lastSpokeVoiceSsrc = uint32(0); - auto lastSpokeAnything = crl::time(0); auto lastSpokeAnythingSsrc = uint32(0); auto removed = _videoStreamSsrcs; for (const auto ssrc : ssrcs) { @@ -1660,30 +1730,41 @@ void GroupCall::setVideoStreams(const std::vector &ssrcs) { _videoStreamSsrcs.emplace(ssrc); _streamsVideoUpdated.fire({ ssrc, true }); } - if (!newLarge) { - const auto j = _lastSpoke.find(ssrc); - if (j != end(_lastSpoke)) { - if (!lastSpokeVoiceSsrc - || lastSpokeVoice < j->second.voice) { + if (!newLarge && real) { + const auto &participants = real->participants(); + const auto i = ranges::find( + participants, + ssrc, + &Data::GroupCallParticipant::ssrc); + if (i != end(participants)) { + if (!lastSpokeVoiceSsrc && i->speaking) { lastSpokeVoiceSsrc = ssrc; - lastSpokeVoice = j->second.voice; } - if (!lastSpokeAnythingSsrc - || lastSpokeAnything < j->second.anything) { + if (!lastSpokeAnythingSsrc && i->sounding) { lastSpokeAnythingSsrc = ssrc; - lastSpokeAnything = j->second.anything; } } } } - if (!newLarge) { + if (!newLarge && real) { + const auto find = [&] { + const auto &participants = real->participants(); + for (const auto ssrc : ssrcs) { + const auto i = ranges::find( + participants, + ssrc, + &Data::GroupCallParticipant::ssrc); + if (i != end(participants)) { + return ssrc; + } + } + return std::uint32_t(0); + }; _videoStreamLarge = lastSpokeVoiceSsrc ? lastSpokeVoiceSsrc : lastSpokeAnythingSsrc ? lastSpokeAnythingSsrc - : ssrcs.empty() - ? 0 - : ssrcs.front(); + : find(); } for (const auto ssrc : removed) { _streamsVideoUpdated.fire({ ssrc, false }); @@ -1932,6 +2013,15 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { }).send(); } +void GroupCall::pinVideoStream(uint32 ssrc) { + if (!ssrc || _videoStreamSsrcs.contains(ssrc)) { + _videoStreamPinned = ssrc; + if (ssrc) { + _videoStreamLarge = ssrc; + } + } +} + void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) { if (input) { _mediaDevices->switchToAudioInput(deviceId); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 7c20314f0..a6773aa61 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -210,12 +210,26 @@ public: -> rpl::producer { return _streamsVideoUpdated.events(); } + [[nodiscard]] bool streamsVideo(uint32 ssrc) const { + return _videoStreamSsrcs.contains(ssrc); + } + [[nodiscard]] uint32 videoStreamPinned() const { + return _videoStreamPinned; + } + void pinVideoStream(uint32 ssrc); [[nodiscard]] uint32 videoStreamLarge() const { return _videoStreamLarge.current(); } [[nodiscard]] rpl::producer videoStreamLargeValue() const { return _videoStreamLarge.value(); } + [[nodiscard]] Webrtc::VideoTrack *videoLargeTrack() const { + return _videoLargeTrack.current(); + } + [[nodiscard]] auto videoLargeTrackValue() const + -> rpl::producer { + return _videoLargeTrack.value(); + } [[nodiscard]] rpl::producer rejoinEvents() const { return _rejoinEvents.events(); } @@ -256,6 +270,7 @@ public: private: using GlobalShortcutValue = base::GlobalShortcutValue; + struct LargeTrack; struct LoadingPart { std::shared_ptr task; @@ -385,6 +400,8 @@ private: base::flat_set _videoStreamSsrcs; rpl::variable _videoStreamLarge = 0; uint32 _videoStreamPinned = 0; + std::unique_ptr _videoLargeTrackWrap; + rpl::variable _videoLargeTrack; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; rpl::event_stream<> _allowedToSpeakNotifications; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 8c7857476..c556bec5e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "window/window_controller.h" // Controller::sessionController. #include "window/window_session_controller.h" +#include "media/view/media_view_pip.h" #include "webrtc/webrtc_video_track.h" #include "styles/style_calls.h" @@ -1147,7 +1148,6 @@ void MembersController::setupListChangeViewers() { } }, _lifetime); - _call->videoStreamLargeValue( ) | rpl::filter([=](uint32 largeSsrc) { return (_largeSsrc != largeSsrc); @@ -1844,6 +1844,17 @@ base::unique_qptr MembersController::createRowContextMenu( _kickParticipantRequests.fire_copy(participantPeer); }); + const auto ssrc = real->ssrc(); + if (ssrc != 0 && _call->streamsVideo(ssrc)) { + const auto pinned = (_call->videoStreamPinned() == ssrc); + const auto phrase = pinned + ? tr::lng_group_call_context_unpin_video(tr::now) + : tr::lng_group_call_context_pin_video(tr::now); + result->addAction(phrase, [=] { + _call->pinVideoStream(pinned ? 0 : ssrc); + }); + } + if (real->ssrc() != 0 && (!isMe(participantPeer) || _peer->canManageGroupCall())) { addMuteActionsToContextMenu(result, participantPeer, admin, real); @@ -2056,9 +2067,13 @@ Members::Members( : RpWidget(parent) , _call(call) , _scroll(this) -, _listController(std::make_unique(call, parent)) { +, _listController(std::make_unique(call, parent)) +, _layout(_scroll->setOwnedWidget( + object_ptr(_scroll.data()))) +, _pinnedVideo(_layout->add(object_ptr(_layout.get()))) { setupAddMember(call); setupList(); + setupPinnedVideo(); setContent(_list); setupFakeRoundCorners(); _listController->setDelegate(static_cast(this)); @@ -2083,15 +2098,17 @@ auto Members::kickParticipantRequests() const } int Members::desiredHeight() const { - const auto top = _addMember ? _addMember->height() : 0; - auto count = [&] { + const auto addMember = _addMemberButton.current(); + const auto top = _pinnedVideo->height() + + (addMember ? addMember->height() : 0); + const auto count = [&] { if (const auto real = _call->lookupReal()) { return real->fullCount(); } return 0; }(); const auto use = std::max(count, _list->fullRowsCount()); - const auto single = (_mode == PanelMode::Wide) + const auto single = (_mode.current() == PanelMode::Wide) ? (st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip) : st::groupCallMembersList.item.height; return top @@ -2137,34 +2154,33 @@ void Members::setupAddMember(not_null call) { _canAddMembers.value( ) | rpl::start_with_next([=](bool can) { if (!can) { + delete _addMemberButton.current(); _addMemberButton = nullptr; - _addMember.destroy(); updateControlsGeometry(); return; + } else if (_addMemberButton.current()) { + return; } - _addMember = Settings::CreateButton( + auto addMember = Settings::CreateButton( this, tr::lng_group_call_invite(), st::groupCallAddMember, &st::groupCallAddMemberIcon, st::groupCallAddMemberIconLeft); - _addMember->show(); - - _addMember->addClickHandler([=] { // TODO throttle(ripple duration) + addMember->show(); + addMember->addClickHandler([=] { // TODO throttle(ripple duration) _addMemberRequests.fire({}); }); - _addMemberButton = _addMember.data(); - - resizeToList(); + _addMemberButton = _layout->insert(1, std::move(addMember)); }, lifetime()); } void Members::setMode(PanelMode mode) { - if (_mode == mode) { + if (_mode.current() == mode) { return; } _mode = mode; - _list->setMode((_mode == PanelMode::Wide) + _list->setMode((mode == PanelMode::Wide) ? PeerListContent::Mode::Custom : PeerListContent::Mode::Default); } @@ -2176,25 +2192,139 @@ rpl::producer Members::fullCountValue() const { void Members::setupList() { _listController->setStyleOverrides(&st::groupCallMembersList); - _list = _scroll->setOwnedWidget(object_ptr( + _list = _layout->add(object_ptr( this, _listController.get())); - _list->heightValue( + _layout->heightValue( ) | rpl::start_with_next([=] { resizeToList(); - }, _list->lifetime()); + }, _layout->lifetime()); rpl::combine( _scroll->scrollTopValue(), _scroll->heightValue() ) | rpl::start_with_next([=](int scrollTop, int scrollHeight) { - _list->setVisibleTopBottom(scrollTop, scrollTop + scrollHeight); + _layout->setVisibleTopBottom(scrollTop, scrollTop + scrollHeight); }, _scroll->lifetime()); updateControlsGeometry(); } +void Members::setupPinnedVideo() { + using namespace rpl::mappers; + + // New video was pinned or mode changed. + rpl::merge( + _mode.changes() | rpl::filter( + _1 == PanelMode::Default + ) | rpl::to_empty, + _call->videoStreamLargeValue() | rpl::filter([=](uint32 ssrc) { + return ssrc == _call->videoStreamPinned(); + }) | rpl::to_empty + ) | rpl::start_with_next([=] { + _scroll->scrollToY(0); + }, _scroll->lifetime()); + + rpl::combine( + _mode.value(), + _call->videoLargeTrackValue() + ) | rpl::map([](PanelMode mode, Webrtc::VideoTrack *track) { + return (mode == PanelMode::Default) ? track : nullptr; + }) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](Webrtc::VideoTrack *track) { + _pinnedTrackLifetime.destroy(); + if (!track) { + _pinnedVideo->resize(_pinnedVideo->width(), 0); + return; + } + const auto frameSize = _pinnedTrackLifetime.make_state(); + const auto applyFrameSize = [=](QSize size) { + const auto width = _pinnedVideo->width(); + if (size.isEmpty() || !width) { + return; + } + const auto heightMin = (width * 9) / 16; + const auto heightMax = (width * 3) / 4; + const auto scaled = size.scaled( + QSize(width, heightMax), + Qt::KeepAspectRatio); + _pinnedVideo->resize( + width, + std::max(scaled.height(), heightMin)); + }; + track->renderNextFrame( + ) | rpl::start_with_next([=] { + const auto size = track->frameSize(); + if (size.isEmpty()) { + track->markFrameShown(); + } else { + if (*frameSize != size) { + *frameSize = size; + applyFrameSize(size); + } + _pinnedVideo->update(); + } + }, _pinnedTrackLifetime); + + _layout->widthValue( + ) | rpl::start_with_next([=] { + applyFrameSize(track->frameSize()); + }, _pinnedTrackLifetime); + + _pinnedVideo->paintRequest( + ) | rpl::start_with_next([=] { + const auto [image, rotation] + = track->frameOriginalWithRotation(); + if (image.isNull()) { + return; + } + auto p = QPainter(_pinnedVideo); + auto hq = PainterHighQualityEnabler(p); + using namespace Media::View; + const auto size = _pinnedVideo->size(); + const auto scaled = FlipSizeByRotation( + image.size(), + rotation + ).scaled(size, Qt::KeepAspectRatio); + const auto left = (size.width() - scaled.width()) / 2; + const auto top = (size.height() - scaled.height()) / 2; + const auto target = QRect(QPoint(left, top), scaled); + if (UsePainterRotation(rotation)) { + if (rotation) { + p.save(); + p.rotate(rotation); + } + p.drawImage(RotatedRect(target, rotation), image); + if (rotation) { + p.restore(); + } + } else if (rotation) { + p.drawImage(target, RotateFrameImage(image, rotation)); + } else { + p.drawImage(target, image); + } + if (left > 0) { + p.fillRect(0, 0, left, size.height(), Qt::black); + } + if (const auto right = left + scaled.width() + ; right < size.width()) { + const auto fill = size.width() - right; + p.fillRect(right, 0, fill, size.height(), Qt::black); + } + if (top > 0) { + p.fillRect(0, 0, size.width(), top, Qt::black); + } + if (const auto bottom = top + scaled.height() + ; bottom < size.height()) { + const auto fill = size.height() - bottom; + p.fillRect(0, bottom, size.width(), fill, Qt::black); + } + track->markFrameShown(); + }, _pinnedTrackLifetime); + }, lifetime()); +} + void Members::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } @@ -2203,11 +2333,8 @@ void Members::resizeToList() { if (!_list) { return; } - const auto listHeight = _list->height(); - const auto newHeight = (listHeight > 0) - ? ((_addMember ? _addMember->height() : 0) - + listHeight - + st::lineWidth) + const auto newHeight = (_list->height() > 0) + ? (_layout->height() + st::lineWidth) : 0; if (height() == newHeight) { updateControlsGeometry(); @@ -2217,17 +2344,8 @@ void Members::resizeToList() { } void Members::updateControlsGeometry() { - if (!_list) { - return; - } - auto topSkip = 0; - if (_addMember) { - _addMember->resizeToWidth(width()); - _addMember->move(0, 0); - topSkip = _addMember->height(); - } - _scroll->setGeometry(0, topSkip, width(), height() - topSkip); - _list->resizeToWidth(width()); + _scroll->setGeometry(rect()); + _layout->resizeToWidth(width()); } void Members::setupFakeRoundCorners() { diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 6a4d5ad2c..3383536cc 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -10,7 +10,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_box.h" namespace Ui { +class RpWidget; class ScrollArea; +class VerticalLayout; class SettingsButton; } // namespace Ui @@ -74,21 +76,25 @@ private: void setupAddMember(not_null call); void resizeToList(); void setupList(); + void setupPinnedVideo(); void setupFakeRoundCorners(); void updateControlsGeometry(); const not_null _call; - PanelMode _mode = PanelMode(); + rpl::variable _mode = PanelMode(); object_ptr _scroll; std::unique_ptr _listController; - object_ptr _addMember = { nullptr }; + not_null _layout; + const not_null _pinnedVideo; rpl::variable _addMemberButton = nullptr; - ListWidget *_list = { nullptr }; + ListWidget *_list = nullptr; rpl::event_stream<> _addMemberRequests; rpl::variable _canAddMembers; + rpl::lifetime _pinnedTrackLifetime; + }; } // namespace Calls diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc index 5270a1dbb..86ca2dd27 160000 --- a/Telegram/lib_webrtc +++ b/Telegram/lib_webrtc @@ -1 +1 @@ -Subproject commit 5270a1dbbdbee643e187e175f798595b4bc49996 +Subproject commit 86ca2dd27e52fa929423b61cd7861a0bc9483e28 From e12fe974b2b4266618ba3c60f33d41a113192cb7 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Apr 2021 19:05:52 +0400 Subject: [PATCH 076/477] Add pinned video in wide mode. --- .../calls/group/calls_group_call.cpp | 13 ++- .../calls/group/calls_group_panel.cpp | 105 +++++++++++++++++- .../calls/group/calls_group_panel.h | 4 + 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 1910027c3..878b9d335 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -410,7 +410,9 @@ void GroupCall::switchToCamera() { return; } _videoDeviceId = _videoInputId; - if (_videoOutgoing) + if (_videoOutgoing->state() != Webrtc::VideoState::Active) { + _videoOutgoing->setState(Webrtc::VideoState::Active); + } _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } @@ -419,6 +421,9 @@ void GroupCall::switchToScreenSharing(const QString &uniqueId) { return; } _videoDeviceId = uniqueId; + if (_videoOutgoing->state() != Webrtc::VideoState::Active) { + _videoOutgoing->setState(Webrtc::VideoState::Active); + } _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } @@ -1432,15 +1437,15 @@ void GroupCall::setupOutgoingVideo() { } _videoOutgoing->stateValue( ) | rpl::start_with_next([=](Webrtc::VideoState state) { - if (state != Webrtc::VideoState::Inactive && !hasDevices()) { + //if (state != Webrtc::VideoState::Inactive && !hasDevices()) { //_errors.fire({ ErrorType::NoCamera }); // #TODO videochats - _videoOutgoing->setState(Webrtc::VideoState::Inactive); + //_videoOutgoing->setState(Webrtc::VideoState::Inactive); //} else if (state != Webrtc::VideoState::Inactive // && _instance // && !_instance->supportsVideo()) { // _errors.fire({ ErrorType::NotVideoCall }); // _videoOutgoing->setState(Webrtc::VideoState::Inactive); - } else if (state != Webrtc::VideoState::Inactive) { + /*} else */if (state != Webrtc::VideoState::Inactive) { // Paused not supported right now. Assert(state == Webrtc::VideoState::Active); if (!_videoCapture) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 4a1dfcb0d..103ddf7ca 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -47,6 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer_rpl.h" #include "app.h" #include "apiwrap.h" // api().kickParticipant. +#include "media/view/media_view_pip.h" +#include "webrtc/webrtc_video_track.h" #include "styles/style_calls.h" #include "styles/style_layers.h" @@ -904,6 +906,91 @@ void Panel::setupMembers() { addMembers(); } }, _callLifetime); + + setupPinnedVideo(); +} + +void Panel::setupPinnedVideo() { + _pinnedVideo.create(widget()); + _pinnedVideo->setVisible(_mode == PanelMode::Wide); + + rpl::combine( + _pinnedVideo->shownValue(), + _call->videoLargeTrackValue() + ) | rpl::map([](bool shown, Webrtc::VideoTrack *track) { + return shown ? track : nullptr; + }) | rpl::distinct_until_changed( + ) | rpl::start_with_next([=](Webrtc::VideoTrack *track) { + _pinnedTrackLifetime.destroy(); + if (!track) { + _pinnedVideo->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(_pinnedVideo.data()).fillRect(clip, Qt::black); + }, _pinnedTrackLifetime); + _pinnedVideo->update(); + return; + } + track->renderNextFrame( + ) | rpl::start_with_next([=] { + const auto size = track->frameSize(); + if (size.isEmpty()) { + track->markFrameShown(); + } else { + _pinnedVideo->update(); + } + }, _pinnedTrackLifetime); + + _pinnedVideo->paintRequest( + ) | rpl::start_with_next([=] { + const auto [image, rotation] + = track->frameOriginalWithRotation(); + if (image.isNull()) { + return; + } + auto p = QPainter(_pinnedVideo); + auto hq = PainterHighQualityEnabler(p); + using namespace Media::View; + const auto size = _pinnedVideo->size(); + const auto scaled = FlipSizeByRotation( + image.size(), + rotation + ).scaled(size, Qt::KeepAspectRatio); + const auto left = (size.width() - scaled.width()) / 2; + const auto top = (size.height() - scaled.height()) / 2; + const auto target = QRect(QPoint(left, top), scaled); + if (UsePainterRotation(rotation)) { + if (rotation) { + p.save(); + p.rotate(rotation); + } + p.drawImage(RotatedRect(target, rotation), image); + if (rotation) { + p.restore(); + } + } else if (rotation) { + p.drawImage(target, RotateFrameImage(image, rotation)); + } else { + p.drawImage(target, image); + } + if (left > 0) { + p.fillRect(0, 0, left, size.height(), Qt::black); + } + if (const auto right = left + scaled.width() + ; right < size.width()) { + const auto fill = size.width() - right; + p.fillRect(right, 0, fill, size.height(), Qt::black); + } + if (top > 0) { + p.fillRect(0, 0, size.width(), top, Qt::black); + } + if (const auto bottom = top + scaled.height() + ; bottom < size.height()) { + const auto fill = size.height() - bottom; + p.fillRect(0, bottom, size.width(), fill, Qt::black); + } + track->markFrameShown(); + }, _pinnedTrackLifetime); + }, widget()->lifetime()); } void Panel::setupJoinAsChangedToasts() { @@ -1402,6 +1489,7 @@ bool Panel::updateMode() { if (_members) { _members->setMode(mode); } + _pinnedVideo->setVisible(mode == PanelMode::Wide); updateControlsGeometry(); return true; } @@ -1457,18 +1545,25 @@ void Panel::updateMembersGeometry() { return; } const auto desiredHeight = _members->desiredHeight(); + const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip; + const auto membersTop = st::groupCallMembersTop; + const auto availableHeight = muteTop + - membersTop + - st::groupCallMembersMargin.bottom(); if (_mode == PanelMode::Wide) { _members->setGeometry( st::groupCallNarrowSkip, 0, st::groupCallNarrowSize.width(), std::min(desiredHeight, widget()->height())); + const auto pinnedLeft = st::groupCallNarrowSkip * 2 + + st::groupCallNarrowSize.width(); + _pinnedVideo->setGeometry( + pinnedLeft, + membersTop, + widget()->width() - pinnedLeft, + availableHeight); } else { - const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip; - const auto membersTop = st::groupCallMembersTop; - const auto availableHeight = muteTop - - membersTop - - st::groupCallMembersMargin.bottom(); const auto membersWidthAvailable = widget()->width() - st::groupCallMembersMargin.left() - st::groupCallMembersMargin.right(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 51e23a5f7..8b6201ab4 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -31,6 +31,7 @@ class CallButton; class CallMuteButton; class IconButton; class FlatLabel; +class RpWidget; template class FadeWrap; template @@ -81,6 +82,7 @@ private: void initGeometry(); void setupScheduledLabels(rpl::producer date); void setupMembers(); + void setupPinnedVideo(); void setupJoinAsChangedToasts(); void setupTitleChangedToasts(); void setupAllowedToSpeakToasts(); @@ -135,6 +137,8 @@ private: object_ptr _menu = { nullptr }; object_ptr _joinAsToggle = { nullptr }; object_ptr _members = { nullptr }; + object_ptr _pinnedVideo = { nullptr }; + rpl::lifetime _pinnedTrackLifetime; object_ptr _startsIn = { nullptr }; object_ptr _countdown = { nullptr }; std::shared_ptr _countdownData; From b7f6fc9a2da200780a337b8a0ddcf76ef5ea7409 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Apr 2021 19:14:14 +0400 Subject: [PATCH 077/477] Update API scheme. --- Telegram/Resources/tl/api.tl | 10 +++++----- .../SourceFiles/calls/group/calls_group_call.cpp | 14 ++++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 93d30d008..720ac01bb 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -68,7 +68,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; -inputMediaInvoice#f4e096c3 flags:# multiple_allowed:flags.1?true can_forward:flags.2?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; +inputMediaInvoice#d9799874 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string = InputMedia; inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; @@ -649,7 +649,7 @@ inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:f inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; -inputBotInlineMessageMediaInvoice#d5348d85 flags:# multiple_allowed:flags.1?true can_forward:flags.3?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaInvoice#d7e78225 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult; @@ -1208,7 +1208,7 @@ groupCall#c95c6654 flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant; +groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_muted:flags.14?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant; phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; @@ -1617,7 +1617,7 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; -phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; +phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_muted:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; @@ -1626,7 +1626,7 @@ phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool; phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates; -phone.editGroupCallParticipant#d975eb80 flags:# muted:flags.0?true call:InputGroupCall participant:InputPeer volume:flags.1?int raise_hand:flags.2?Bool = Updates; +phone.editGroupCallParticipant#aec610e4 flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_muted:flags.3?Bool = Updates; phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates; phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers; phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 878b9d335..667bdc18a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1998,13 +1998,13 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { _updateMuteRequestId = _api.request(MTPphone_EditGroupCallParticipant( MTP_flags((type == SendUpdateType::RaiseHand) ? Flag::f_raise_hand - : (muted() != MuteState::Active) - ? Flag::f_muted - : Flag(0)), + : Flag::f_muted), inputCall(), _joinAs->input, + MTP_bool(muted() != MuteState::Active), MTP_int(100000), // volume - MTP_bool(muted() == MuteState::RaisedHand) + MTP_bool(muted() == MuteState::RaisedHand), + MTPBool() // video_muted )).done([=](const MTPUpdates &result) { _updateMuteRequestId = 0; _peer->session().api().applyUpdates(result); @@ -2066,14 +2066,16 @@ void GroupCall::editParticipant( applyParticipantLocally(participantPeer, mute, volume); using Flag = MTPphone_EditGroupCallParticipant::Flag; - const auto flags = (mute ? Flag::f_muted : Flag(0)) + const auto flags = Flag::f_muted | (volume.has_value() ? Flag::f_volume : Flag(0)); _api.request(MTPphone_EditGroupCallParticipant( MTP_flags(flags), inputCall(), participantPeer->input, + MTP_bool(mute), MTP_int(std::clamp(volume.value_or(0), 1, Group::kMaxVolume)), - MTPBool() + MTPBool(), // raise_hand + MTPBool() // video_muted )).done([=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); }).fail([=](const MTP::Error &error) { From 380a0d1f866b73fabb4cccace2fcd79ddb4dcc01 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Apr 2021 20:54:51 +0400 Subject: [PATCH 078/477] Improve wide video layout. --- Telegram/SourceFiles/calls/calls.style | 4 ++-- Telegram/SourceFiles/calls/group/calls_group_call.cpp | 3 ++- Telegram/SourceFiles/calls/group/calls_group_panel.cpp | 2 +- Telegram/ThirdParty/tgcalls | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 7588e7387..829b0a83f 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1022,5 +1022,5 @@ desktopCaptureSourceSkip: 12px; groupCallNarrowSkip: 9px; groupCallNarrowRowSkip: 8px; groupCallNarrowSize: size(90px, 90px); -groupCallWideModeWidthMin: 800px; -groupCallWideModeSize: size(960px, 520px); +groupCallWideModeWidthMin: 520px; +groupCallWideModeSize: size(720px, 480px); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 667bdc18a..597842983 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -475,7 +475,8 @@ void GroupCall::subscribeToReal(not_null real) { _videoStreamLarge = bestWithVideoSsrc; } } else if ((nowSpeaking || nowSounding) - && (data.now->ssrc != videoLargeSsrc)) { + && (data.now->ssrc != videoLargeSsrc) + && _videoStreamSsrcs.contains(data.now->ssrc)) { const auto i = ranges::find( participants, videoLargeSsrc, diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 103ddf7ca..54db5465b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -1561,7 +1561,7 @@ void Panel::updateMembersGeometry() { _pinnedVideo->setGeometry( pinnedLeft, membersTop, - widget()->width() - pinnedLeft, + widget()->width() - pinnedLeft - st::groupCallNarrowSkip, availableHeight); } else { const auto membersWidthAvailable = widget()->width() diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 0f608993f..4a944bc29 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 0f608993f7a5c9af093f6a7bf21c3b4cd0735ecc +Subproject commit 4a944bc29ec308556471a1533874a68dd95a9ba3 From 24c77a8956202abe209fbe1e339abe7a5d31268d Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 23 Apr 2021 21:36:44 +0400 Subject: [PATCH 079/477] Fix build on macOS. --- Telegram/cmake/lib_tgcalls.cmake | 2 -- 1 file changed, 2 deletions(-) diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index fb465f4f1..773ce44c6 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -167,8 +167,6 @@ elseif (APPLE) remove_target_sources(lib_tgcalls ${tgcalls_loc} platform/darwin/DesktopCaptureSourceView.h platform/darwin/DesktopCaptureSourceView.mm - platform/darwin/DesktopSharingCapturer.h - platform/darwin/DesktopSharingCapturer.mm platform/darwin/GLVideoView.h platform/darwin/GLVideoView.mm platform/darwin/GLVideoViewMac.h From e0bfaad3a2aad5abd8f58590e93da6354203ee02 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 26 Apr 2021 13:21:01 +0400 Subject: [PATCH 080/477] Make style of mute button customizable. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/calls/calls.style | 83 ++++- .../calls/group/calls_group_panel.cpp | 78 +++-- .../calls/group/calls_group_panel.h | 3 + .../ui/controls/call_mute_button.cpp | 299 ++++++++++-------- .../ui/controls/call_mute_button.h | 20 +- 6 files changed, 326 insertions(+), 158 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6184a552f..051901579 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2010,6 +2010,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_force_muted_sub" = "You are in Listen Only mode"; "lng_group_call_raise_hand_tip" = "Click if you want to speak"; "lng_group_call_raised_hand" = "You asked to speak"; +"lng_group_call_raised_hand_small" = "Raised hand"; "lng_group_call_raised_hand_sub" = "We let the speakers know"; "lng_group_call_connecting" = "Connecting..."; "lng_group_call_leave" = "Leave"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 829b0a83f..a617e585e 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -175,6 +175,87 @@ callCameraUnmute: CallButton(callMicrophoneUnmute) { } callBottomShadowSize: 124px; +CallMuteButton { + active: CallButton; + muted: CallButton; + labelAdditional: pixels; + sublabel: FlatLabel; + labelsSkip: pixels; + sublabelSkip: pixels; + lottieSize: size; + lottieTop: pixels; +} + +callMuteButtonLabel: FlatLabel(defaultFlatLabel) { + textFg: groupCallMembersFg; + style: TextStyle(defaultTextStyle) { + font: font(14px); + linkFont: font(14px); + linkFontOver: font(14px underline); + } +} +callMuteButtonActiveInner: IconButton { + width: 136px; + height: 165px; +} +callMuteButtonSmallActiveInner: IconButton { + width: 68px; + height: 79px; +} +callMuteButtonActive: CallButton { + button: callMuteButtonActiveInner; + bg: groupCallLive1; + bgSize: 100px; + bgPosition: point(18px, 18px); + outerRadius: 18px; + outerBg: callAnswerBgOuter; + label: callMuteButtonLabel; +} +callMuteButton: CallMuteButton { + active: callMuteButtonActive; + muted: CallButton(callMuteButtonActive) { + bg: groupCallMuted1; + label: callMuteButtonLabel; + } + labelAdditional: 5px; + sublabel: FlatLabel(defaultFlatLabel) { + textFg: groupCallMemberNotJoinedStatus; + } + labelsSkip: 5px; + sublabelSkip: 19px; + lottieSize: size(67px, 67px); + lottieTop: 35px; +} +callMuteButtonSmallActive: CallButton(callMuteButtonActive) { + button: callMuteButtonSmallActiveInner; + bgSize: 44px; + bgPosition: point(12px, 12px); + outerRadius: 12px; + label: callButtonLabel; +} +callMuteButtonSmall: CallMuteButton(callMuteButton) { + active: callMuteButtonSmallActive; + muted: CallButton(callMuteButtonSmallActive) { + bg: groupCallMuted1; + label: callButtonLabel; + } + labelsSkip: 0px; + sublabelSkip: 0px; + lottieSize: size(36px, 36px); + lottieTop: 17px; +} + +callMuteMinorBlobMinRadius: 64px; +callMuteMinorBlobMaxRadius: 74px; +callMuteMajorBlobMinRadius: 67px; +callMuteMajorBlobMaxRadius: 77px; +callMuteBlobRadiusForDiameter: 100px; + +callConnectingRadial: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + color: lightButtonFg; + thickness: 4px; +} + callName: FlatLabel(defaultFlatLabel) { minWidth: 260px; maxHeight: 30px; @@ -442,8 +523,6 @@ callErrorToast: Toast(defaultToast) { groupCallWidth: 380px; groupCallHeight: 580px; -groupCallMuteButtonIconSize: size(67px, 67px); -groupCallMuteButtonIconTop: 35px; groupCallRipple: RippleAnimation(defaultRippleAnimation) { color: groupCallMembersBgRipple; } diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 54db5465b..cb0e2de1d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -385,8 +385,10 @@ Panel::Panel(not_null call) _window->body(), st::groupCallTitle)) #endif // !Q_OS_MAC +, _videoMode(true) // #TODO calls , _mute(std::make_unique( widget(), + st::callMuteButton, Core::App().appDeactivatedValue(), Ui::CallMuteButtonState{ .text = (_call->scheduleDate() @@ -738,7 +740,8 @@ void Panel::setupRealMuteButtonState(not_null real) { _call->instanceStateValue(), real->scheduleDateValue(), real->scheduleStartSubscribedValue(), - Data::CanManageGroupCallValue(_peer) + Data::CanManageGroupCallValue(_peer), + _videoMode.value() ) | rpl::distinct_until_changed( ) | rpl::filter( _2 != GroupCall::InstanceState::TransitionToRtc @@ -747,7 +750,8 @@ void Panel::setupRealMuteButtonState(not_null real) { GroupCall::InstanceState state, TimeId scheduleDate, bool scheduleStartSubscribed, - bool canManage) { + bool canManage, + bool videoMode) { using Type = Ui::CallMuteButtonType; _mute->setState(Ui::CallMuteButtonState{ .text = (scheduleDate @@ -759,13 +763,19 @@ void Panel::setupRealMuteButtonState(not_null real) { : state == GroupCall::InstanceState::Disconnected ? tr::lng_group_call_connecting(tr::now) : mute == MuteState::ForceMuted - ? tr::lng_group_call_force_muted(tr::now) + ? (videoMode + ? tr::lng_group_call_force_muted_small(tr::now) + : tr::lng_group_call_force_muted(tr::now)) : mute == MuteState::RaisedHand - ? tr::lng_group_call_raised_hand(tr::now) + ? (videoMode + ? tr::lng_group_call_raised_hand_small(tr::now) + : tr::lng_group_call_raised_hand(tr::now)) : mute == MuteState::Muted ? tr::lng_group_call_unmute(tr::now) - : tr::lng_group_call_you_are_live(tr::now)), - .subtext = (scheduleDate + : (videoMode + ? tr::lng_group_call_you_are_live_small(tr::now) + : tr::lng_group_call_you_are_live(tr::now))), + .subtext = ((scheduleDate || videoMode) ? QString() : state == GroupCall::InstanceState::Disconnected ? QString() @@ -1498,22 +1508,48 @@ void Panel::updateControlsGeometry() { if (widget()->size().isEmpty() || (!_settings && !_share)) { return; } - const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip; - const auto buttonsTop = widget()->height() - st::groupCallButtonBottomSkip; - const auto muteSize = _mute->innerSize().width(); - const auto fullWidth = muteSize - + 2 * (_settings ? _settings : _share)->width() - + 2 * st::groupCallButtonSkip; - _mute->moveInner({ (widget()->width() - muteSize) / 2, muteTop }); - const auto leftButtonLeft = (widget()->width() - fullWidth) / 2; - if (_settings) { - _settings->moveToLeft(leftButtonLeft, buttonsTop); + if (_videoMode.current()) { + _mute->setStyle(st::callMuteButtonSmall); + const auto buttonsTop = widget()->height() + - st::groupCallButtonBottomSkip; + const auto muteSize = _mute->innerSize().width(); + const auto fullWidth = muteSize + + 2 * (_settings ? _settings : _share)->width() + + 2 * st::groupCallButtonSkip; + const auto leftButtonLeft = (widget()->width() - fullWidth) / 2; + const auto addSkip = st::callMuteButtonSmall.active.outerRadius; + _mute->moveInner({ leftButtonLeft + addSkip, buttonsTop + addSkip }); + if (_settings) { + _settings->moveToLeft( + (widget()->width() - _settings->width()) / 2, + buttonsTop); + } + if (_share) { + _share->moveToLeft( + (widget()->width() - _share->width()) / 2, + buttonsTop); + } + _hangup->moveToRight(leftButtonLeft, buttonsTop); + } else { + _mute->setStyle(st::callMuteButton); + const auto muteTop = widget()->height() + - st::groupCallMuteBottomSkip; + const auto buttonsTop = widget()->height() + - st::groupCallButtonBottomSkip; + const auto muteSize = _mute->innerSize().width(); + const auto fullWidth = muteSize + + 2 * (_settings ? _settings : _share)->width() + + 2 * st::groupCallButtonSkip; + _mute->moveInner({ (widget()->width() - muteSize) / 2, muteTop }); + const auto leftButtonLeft = (widget()->width() - fullWidth) / 2; + if (_settings) { + _settings->moveToLeft(leftButtonLeft, buttonsTop); + } + if (_share) { + _share->moveToLeft(leftButtonLeft, buttonsTop); + } + _hangup->moveToRight(leftButtonLeft, buttonsTop); } - if (_share) { - _share->moveToLeft(leftButtonLeft, buttonsTop); - } - _hangup->moveToRight(leftButtonLeft, buttonsTop); - updateMembersGeometry(); refreshTitle(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 8b6201ab4..a963fbf62 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -144,9 +144,12 @@ private: std::shared_ptr _countdownData; object_ptr _startsWhen = { nullptr }; ChooseJoinAsProcess _joinAsProcess; + rpl::variable _videoMode; object_ptr _settings = { nullptr }; object_ptr _share = { nullptr }; + object_ptr _video = { nullptr }; + object_ptr _shareScreen = { nullptr }; std::unique_ptr _mute; object_ptr _hangup; Fn _shareLinkCallback; diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp index 87fd85e1f..8a5b6ad0a 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp @@ -274,8 +274,10 @@ class BlobsWidget final : public RpWidget { public: BlobsWidget( not_null parent, + int diameter, rpl::producer &&hideBlobs); + void setDiameter(int diameter); void setLevel(float level); void setBlobBrush(QBrush brush); void setGlowBrush(QBrush brush); @@ -286,11 +288,12 @@ public: void setSwitchConnectingProgress(float64 progress); private: - void init(); + void init(int diameter); + void computeCircleRect(); Paint::Blobs _blobs; - const float _circleRadius; + float _circleRadius = 0.; QBrush _blobBrush; QBrush _glowBrush; int _center = 0; @@ -312,15 +315,15 @@ private: BlobsWidget::BlobsWidget( not_null parent, + int diameter, rpl::producer &&hideBlobs) : RpWidget(parent) , _blobs(MuteBlobs(), kLevelDuration, kMaxLevel) -, _circleRadius(st::callMuteButtonActive.bgSize / 2.) , _blobBrush(Qt::transparent) , _glowBrush(Qt::transparent) , _blobsLastTime(crl::now()) , _blobsScaleLastTime(crl::now()) { - init(); + init(diameter); std::move( hideBlobs @@ -342,7 +345,27 @@ BlobsWidget::BlobsWidget( }, lifetime()); } -void BlobsWidget::init() { +void BlobsWidget::setDiameter(int diameter) { + _circleRadius = diameter / 2.; + const auto defaultSize = _blobs.maxRadius() * 2 * kGlowPaddingFactor; + const auto s = int(std::ceil((defaultSize * diameter) + / float64(st::callMuteBlobRadiusForDiameter))); + const auto size = QSize{ s, s }; + if (this->size() != size) { + resize(size); + } + computeCircleRect(); +} + +void BlobsWidget::computeCircleRect() { + const auto &r = _circleRadius; + const auto left = (size().width() - r * 2.) / 2.; + const auto add = st::callConnectingRadial.thickness / 2; + _circleRect = QRectF(left, left, r * 2, r * 2).marginsAdded( + style::margins(add, add, add, add)); +} + +void BlobsWidget::init(int diameter) { setAttribute(Qt::WA_TransparentForMouseEvents); const auto cutRect = [](Painter &p, const QRectF &r) { @@ -354,22 +377,12 @@ void BlobsWidget::init() { p.restore(); }; - { - const auto s = _blobs.maxRadius() * 2 * kGlowPaddingFactor; - resize(s, s); - } + setDiameter(diameter); sizeValue( ) | rpl::start_with_next([=](QSize size) { _center = size.width() / 2; - - { - const auto &r = _circleRadius; - const auto left = (size.width() - r * 2.) / 2.; - const auto add = st::callConnectingRadial.thickness / 2; - _circleRect = QRectF(left, left, r * 2, r * 2).marginsAdded( - style::margins(add, add, add, add)); - } + computeCircleRect(); }, lifetime()); paintRequest( @@ -396,7 +409,9 @@ void BlobsWidget::init() { _blobsScaleEnter * (1. - Clamp( _switchConnectingProgress / kBlobPartAnimation))) : _blobsScaleEnter; - _blobs.paint(p, _blobBrush, scale); + const auto sizeScale = (2. * _circleRadius) + / st::callMuteBlobRadiusForDiameter; + _blobs.paint(p, _blobBrush, scale * sizeScale); p.translate(-_center, -_center); if (scale < 1.) { @@ -499,12 +514,14 @@ void BlobsWidget::setSwitchConnectingProgress(float64 progress) { CallMuteButton::CallMuteButton( not_null parent, + const style::CallMuteButton &st, rpl::producer &&hideBlobs, CallMuteButtonState initial) : _state(initial) -, _st(st::callMuteButtonActive) +, _st(&st) , _blobs(base::make_unique_q( parent, + _st->active.bgSize, rpl::combine( rpl::single(anim::Disabled()) | rpl::then(anim::Disables()), std::move(hideBlobs), @@ -516,53 +533,82 @@ CallMuteButton::CallMuteButton( return isBadState || !(!animDisabled && !hide); }))) , _content(base::make_unique_q(parent)) -, _centerLabel(base::make_unique_q( - parent, - _state.value( - ) | rpl::map([](const CallMuteButtonState &state) { - return state.subtext.isEmpty() ? state.text : QString(); - }), - kSwitchLabelDuration, - st::callMuteButtonLabelAdditional, - _st.label)) -, _label(base::make_unique_q( - parent, - _state.value( - ) | rpl::map([](const CallMuteButtonState &state) { - return state.subtext.isEmpty() ? QString() : state.text; - }), - kSwitchLabelDuration, - st::callMuteButtonLabelAdditional, - _st.label)) -, _sublabel(base::make_unique_q( - parent, - _state.value( - ) | rpl::map([](const CallMuteButtonState &state) { - return state.subtext; - }), - kSwitchLabelDuration, - st::callMuteButtonLabelAdditional, - st::callMuteButtonSublabel)) , _colors(Colors()) -, _iconState(initialState()) { +, _iconState(iconStateFrom(initial.type)) { init(); } -CallMuteButton::IconState CallMuteButton::initialState() { - const auto result = iconStateFrom(_state.current().type); +void CallMuteButton::refreshLabels() { + _centerLabel = base::make_unique_q( + _content->parentWidget(), + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.subtext.isEmpty() ? state.text : QString(); + }), + kSwitchLabelDuration, + _st->labelAdditional, + _st->active.label); + _label = base::make_unique_q( + _content->parentWidget(), + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.subtext.isEmpty() ? QString() : state.text; + }), + kSwitchLabelDuration, + _st->labelAdditional, + _st->active.label); + _sublabel = base::make_unique_q( + _content->parentWidget(), + _state.value( + ) | rpl::map([](const CallMuteButtonState &state) { + return state.subtext; + }), + kSwitchLabelDuration, + _st->labelAdditional, + _st->sublabel); + + _label->show(); + rpl::combine( + _content->geometryValue(), + _label->sizeValue() + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateLabelGeometry(my, size); + }, _label->lifetime()); + _label->setAttribute(Qt::WA_TransparentForMouseEvents); + + _sublabel->show(); + rpl::combine( + _content->geometryValue(), + _sublabel->sizeValue() + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateSublabelGeometry(my, size); + }, _sublabel->lifetime()); + _sublabel->setAttribute(Qt::WA_TransparentForMouseEvents); + + _centerLabel->show(); + rpl::combine( + _content->geometryValue(), + _centerLabel->sizeValue() + ) | rpl::start_with_next([=](QRect my, QSize size) { + updateCenterLabelGeometry(my, size); + }, _centerLabel->lifetime()); + _centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void CallMuteButton::refreshIcons() { _icons[0].emplace(Lottie::IconDescriptor{ .path = u":/gui/icons/calls/voice.lottie"_q, .color = st::groupCallIconFg, - .sizeOverride = st::groupCallMuteButtonIconSize, - .frame = result.frameTo, + .sizeOverride = _st->lottieSize, + .frame = (_iconState.index ? 0 : _iconState.frameTo), }); _icons[1].emplace(Lottie::IconDescriptor{ .path = u":/gui/icons/calls/hands.lottie"_q, .color = st::groupCallIconFg, - .sizeOverride = st::groupCallMuteButtonIconSize, - .frame = 0, + .sizeOverride = _st->lottieSize, + .frame = (_iconState.index ? _iconState.frameTo : 0), }); - return result; + } auto CallMuteButton::iconStateAnimated(CallMuteButtonType previous) @@ -647,35 +693,11 @@ CallMuteButton::IconState CallMuteButton::randomWavingState() { } void CallMuteButton::init() { - _content->resize(_st.button.width, _st.button.height); + refreshLabels(); + refreshIcons(); - // Label text. - _label->show(); - rpl::combine( - _content->geometryValue(), - _label->sizeValue() - ) | rpl::start_with_next([=](QRect my, QSize size) { - updateLabelGeometry(my, size); - }, _label->lifetime()); - _label->setAttribute(Qt::WA_TransparentForMouseEvents); - - _sublabel->show(); - rpl::combine( - _content->geometryValue(), - _sublabel->sizeValue() - ) | rpl::start_with_next([=](QRect my, QSize size) { - updateSublabelGeometry(my, size); - }, _sublabel->lifetime()); - _sublabel->setAttribute(Qt::WA_TransparentForMouseEvents); - - _centerLabel->show(); - rpl::combine( - _content->geometryValue(), - _centerLabel->sizeValue() - ) | rpl::start_with_next([=](QRect my, QSize size) { - updateCenterLabelGeometry(my, size); - }, _centerLabel->lifetime()); - _centerLabel->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto &button = _st->active.button; + _content->resize(button.width, button.height); rpl::combine( _radialInfo.rawShowProgress.value(), @@ -722,36 +744,7 @@ void CallMuteButton::init() { lifetime().make_state(_state.current().type); setHandleMouseState(HandleMouseState::Disabled); - const auto blobsInner = [&] { - // The point of the circle at 45 degrees. - const auto w = _blobs->innerRect().width(); - const auto mF = (1 - std::cos(M_PI / 4.)) * (w / 2.); - return _blobs->innerRect().marginsRemoved(QMarginsF(mF, mF, mF, mF)); - }(); - - auto linearGradients = anim::linear_gradients( - _colors, - QPointF(blobsInner.x() + blobsInner.width(), blobsInner.y()), - QPointF(blobsInner.x(), blobsInner.y() + blobsInner.height())); - - auto glowColors = [&] { - auto copy = _colors; - for (auto &[type, stops] : copy) { - auto firstColor = IsInactive(type) - ? st::groupCallBg->c - : stops.stops[(stops.stops.size() - 1) / 2].second; - firstColor.setAlpha(kGlowAlpha); - stops.stops = QGradientStops{ - { 0., std::move(firstColor) }, - { 1., QColor(Qt::transparent) } - }; - } - return copy; - }(); - auto glows = anim::radial_gradients( - std::move(glowColors), - blobsInner.center(), - _blobs->width() / 2); + refreshGradients(); _state.value( ) | rpl::map([](const CallMuteButtonState &state) { @@ -784,9 +777,9 @@ void CallMuteButton::init() { auto callback = [=](float64 value) { const auto brushProgress = fromConnecting ? 1. : value; _blobs->setBlobBrush(QBrush( - linearGradients.gradient(previous, type, brushProgress))); + _linearGradients.gradient(previous, type, brushProgress))); _blobs->setGlowBrush(QBrush( - glows.gradient(previous, type, value))); + _glowGradients.gradient(previous, type, value))); _blobs->update(); const auto radialShowProgress = (radialShowFrom == radialShowTo) @@ -815,10 +808,10 @@ void CallMuteButton::init() { // Icon rect. _content->sizeValue( ) | rpl::start_with_next([=](QSize size) { - const auto icon = st::groupCallMuteButtonIconSize; + const auto icon = _st->lottieSize; _muteIconRect = QRect( (size.width() - icon.width()) / 2, - st::groupCallMuteButtonIconTop, + _st->lottieTop, icon.width(), icon.height()); }, lifetime()); @@ -850,8 +843,8 @@ void CallMuteButton::init() { InfiniteRadialAnimation::Draw( p, r, - _st.bgPosition, - _radialInfo.st.size, + _st->active.bgPosition, + QSize(_st->active.bgSize, _st->active.bgSize), _content->width(), QPen(_radialInfo.st.color), _radialInfo.st.thickness); @@ -862,8 +855,8 @@ void CallMuteButton::init() { InfiniteRadialAnimation::Draw( p, std::move(state), - _st.bgPosition, - _radialInfo.st.size, + _st->active.bgPosition, + QSize(_st->active.bgSize, _st->active.bgSize), _content->width(), QPen(_radialInfo.st.color), _radialInfo.st.thickness); @@ -871,6 +864,39 @@ void CallMuteButton::init() { }, _content->lifetime()); } +void CallMuteButton::refreshGradients() { + const auto blobsInner = [&] { + // The point of the circle at 45 degrees. + const auto w = _blobs->innerRect().width(); + const auto mF = (1 - std::cos(M_PI / 4.)) * (w / 2.); + return _blobs->innerRect().marginsRemoved(QMarginsF(mF, mF, mF, mF)); + }(); + + _linearGradients = anim::linear_gradients( + _colors, + QPointF(blobsInner.x() + blobsInner.width(), blobsInner.y()), + QPointF(blobsInner.x(), blobsInner.y() + blobsInner.height())); + + auto glowColors = [&] { + auto copy = _colors; + for (auto &[type, stops] : copy) { + auto firstColor = IsInactive(type) + ? st::groupCallBg->c + : stops.stops[(stops.stops.size() - 1) / 2].second; + firstColor.setAlpha(kGlowAlpha); + stops.stops = QGradientStops{ + { 0., std::move(firstColor) }, + { 1., QColor(Qt::transparent) } + }; + } + return copy; + }(); + _glowGradients = anim::radial_gradients( + std::move(glowColors), + blobsInner.center(), + _blobs->width() / 2); +} + void CallMuteButton::scheduleIconState(const IconState &state) { if (_iconState != state) { if (_icons[_iconState.index]->animating()) { @@ -906,28 +932,26 @@ void CallMuteButton::updateLabelsGeometry() { } void CallMuteButton::updateLabelGeometry(QRect my, QSize size) { - const auto skip = st::callMuteButtonSublabelSkip - + st::callMuteButtonLabelsSkip; + const auto skip = _st->sublabelSkip + _st->labelsSkip; _label->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - _label->height() - skip, + my.y() + my.height() - size.height() - skip, my.width()); } void CallMuteButton::updateCenterLabelGeometry(QRect my, QSize size) { - const auto skip = (st::callMuteButtonSublabelSkip / 2) - + st::callMuteButtonLabelsSkip; + const auto skip = (_st->sublabelSkip / 2) + _st->labelsSkip; _centerLabel->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - _centerLabel->height() - skip, + my.y() + my.height() - size.height() - skip, my.width()); } void CallMuteButton::updateSublabelGeometry(QRect my, QSize size) { - const auto skip = st::callMuteButtonLabelsSkip; + const auto skip = _st->labelsSkip; _sublabel->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - _sublabel->height() - skip, + my.y() + my.height() - size.height() - skip, my.width()); } @@ -978,6 +1002,21 @@ CallMuteButton::HandleMouseState CallMuteButton::HandleMouseStateFromType( Unexpected("Type in HandleMouseStateFromType."); } +void CallMuteButton::setStyle(const style::CallMuteButton &st) { + if (_st == &st) { + return; + } + _st = &st; + const auto &button = _st->active.button; + _content->resize(button.width, button.height); + _blobs->setDiameter(_st->active.bgSize); + + refreshIcons(); + refreshLabels(); + updateLabelsGeometry(); + refreshGradients(); +} + void CallMuteButton::setState(const CallMuteButtonState &state) { _state = state; } @@ -1002,7 +1041,7 @@ QSize CallMuteButton::innerSize() const { } QRect CallMuteButton::innerGeometry() const { - const auto &skip = _st.outerRadius; + const auto &skip = _st->active.outerRadius; return QRect( _content->x(), _content->y(), @@ -1011,7 +1050,7 @@ QRect CallMuteButton::innerGeometry() const { } void CallMuteButton::moveInner(QPoint position) { - const auto &skip = _st.outerRadius; + const auto &skip = _st->active.outerRadius; _content->move(position - QPoint(skip, skip)); { diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.h b/Telegram/SourceFiles/ui/controls/call_mute_button.h index 924489acc..7759c3942 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.h +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.h @@ -13,6 +13,10 @@ #include "ui/effects/radial_animation.h" #include "lottie/lottie_icon.h" +namespace style { +struct CallMuteButton; +} // namespace style + namespace st { extern const style::InfiniteRadialAnimation &callConnectingRadial; } // namespace st @@ -49,11 +53,13 @@ class CallMuteButton final { public: explicit CallMuteButton( not_null parent, + const style::CallMuteButton &st, rpl::producer &&hideBlobs, CallMuteButtonState initial = CallMuteButtonState()); ~CallMuteButton(); void setState(const CallMuteButtonState &state); + void setStyle(const style::CallMuteButton &st); void setLevel(float level); [[nodiscard]] rpl::producer clicks(); @@ -113,6 +119,9 @@ private: }; void init(); + void refreshIcons(); + void refreshGradients(); + void refreshLabels(); void overridesColors( CallMuteButtonType fromType, CallMuteButtonType toType, @@ -124,7 +133,6 @@ private: void updateSublabelGeometry(QRect my, QSize size); void updateLabelsGeometry(); - [[nodiscard]] IconState initialState(); [[nodiscard]] IconState iconStateFrom(CallMuteButtonType previous); [[nodiscard]] IconState randomWavingState(); [[nodiscard]] IconState iconStateAnimated(CallMuteButtonType previous); @@ -140,18 +148,20 @@ private: QRect _muteIconRect; HandleMouseState _handleMouseState = HandleMouseState::Enabled; - const style::CallButton &_st; + not_null _st; const base::unique_qptr _blobs; const base::unique_qptr _content; - const base::unique_qptr _centerLabel; - const base::unique_qptr _label; - const base::unique_qptr _sublabel; + base::unique_qptr _centerLabel; + base::unique_qptr _label; + base::unique_qptr _sublabel; int _labelShakeShift = 0; RadialInfo _radialInfo; std::unique_ptr _radial; const base::flat_map _colors; + anim::linear_gradients _linearGradients; + anim::radial_gradients _glowGradients; std::array, 2> _icons; IconState _iconState; From b7fc3f67d7dd203586758e7d77db92e7ef1be4b1 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Apr 2021 16:03:36 +0400 Subject: [PATCH 081/477] Add toggle video and screen sharing buttons. --- .../Resources/icons/calls/calls_present.png | Bin 0 -> 553 bytes .../icons/calls/calls_present@2x.png | Bin 0 -> 991 bytes .../icons/calls/calls_present@3x.png | Bin 0 -> 1529 bytes Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/calls/calls.style | 109 ++++++++---------- .../calls/group/calls_group_call.cpp | 15 ++- .../calls/group/calls_group_call.h | 2 +- .../calls/group/calls_group_panel.cpp | 99 ++++++++++------ .../calls/group/calls_group_panel.h | 6 +- .../ui/controls/call_mute_button.cpp | 32 ++--- Telegram/lib_ui | 2 +- 11 files changed, 151 insertions(+), 115 deletions(-) create mode 100644 Telegram/Resources/icons/calls/calls_present.png create mode 100644 Telegram/Resources/icons/calls/calls_present@2x.png create mode 100644 Telegram/Resources/icons/calls/calls_present@3x.png diff --git a/Telegram/Resources/icons/calls/calls_present.png b/Telegram/Resources/icons/calls/calls_present.png new file mode 100644 index 0000000000000000000000000000000000000000..5e0ba09504b4b3ad54acb2133b7cc0c134cabedc GIT binary patch literal 553 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY_g||V~7Xu z)ezrRhYSSzMDJw1T-4q5;GXgg=87ec&b6{@9_{76{^*!8hn|ao`L;)*TMm6Us?;|# z^St<}<^!A4rJq0dojha0dracTuNfBd&i@$yNjJrW~0zrQW>`qfIM`$iHxubM)Rin6>di?;Ave);%Xm7lAv zr*?mLQN6;Max#up_xDO$!@kVbS69v9nzij|QRdf|WxM^ixif8Y;ctC9Ayrc5xTDN* zF@C$(M?+q!ZgMMhp7%ULM~rE~&735&*^+nftL?a(C((cWMTu2bnZl%>HR~qsadmi* zC}GE_&9LC3UUyRZf_0N`R0+6F6t&@ClIcHgwC~mVBX@*4J0H~TKU^Bi)9$R|*>pCo z`S3#phMN1w>-Jwi;Q6tH`KhzSm5#f4?I%;1lqMC~&3{-ax9#@Ykj&jpEA4}3ZFel( z8N+kSNtLt5K5EvoJ8`;uY{J@$_C)CyF5B*6w(rEtt-?oF<_9(}cU&MZoZMMA`R#w< Z8pg{)`Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NFNl8RORA>e5SV2oVK@=bVNu5MG z2niC>SLkd=9xW6EkDa29U4_1Y_!+u*P!K!?dgu@#2!RJrUc5#(H zMrYUk?<)9UnAzER^M1cKZ|2Rek1+;V)>i(lE%Gt&4FRuLaI*p{w*s+PjNRVevghY# z1_1xPkezb5%p#G9ZC}taYLx5iYZwlPh}(U6e}4xgF~PzyY1AX|=y}h;b9doN*e8Ht z!{_m=#&EJvfRsh~`1pX!%S(txqjn~COJ@{9BKXhGPdGR@a0|pTcH{(dx!f|7x~8j4 zWDgGycmS-vC<)N{Y?kfqZI;jHUCR#`(nb!_$rHwMi+f;ye}5_r5WG0V)#o{RLcRw? z051*_?XkPNJICm| zO5pYNl~GBj)9n8K-VqY665xoxzP{M$=_zY88obMP?Fj)w+wb>TGMQw}X45u!uBep< zmN_!t-rk^CEJ7-kn#wH*00MykJU%{ZPft2R(aQtc7EA*P^wH4~Y;A3cMyH;G!639+ zEt96YDS)SWoF{-A6)?u9Kyo{(dUtmh+U>R)`S^`Q-&q20EdT%wmgrhfxbU| zvsfiisZ_*?XP#A?`ReM*YnH>p)~JyZR+Bt(K%g2KrP0s4L>&@^c#lYwrxD+NPGrO7ayot;_! zm1EW)xBm!8UQP^35sf?R^*ZeA>_D|zoj1%l>$#0=HVd?GtP0oXwIinxojSEzjU^HZ zoL4PApU-%S8JU$zC5C%KJ=nsViI-7F*|zE_+4!HIAGo=>f$#5c*=wyn5hJ;TKra-W zec>242JltnwzO}fFE1}#$Fo|7L7TuT{3lXjP{uvFdh$-p6Iuc5;Gl)SRm@;jZF8P* z5Zm0TC?`>FBHJ=t+M5`aS5mb1z)GS=P#XeXugqozHY>2C2i87>egUNB#ecI)t^@!8 N002ovPDHLkV1gy$uE78R literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/calls/calls_present@3x.png b/Telegram/Resources/icons/calls/calls_present@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ae36d18bda8215391a89748b6d850e1d0df9d7e0 GIT binary patch literal 1529 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS=V@X6oRCodHo7*crK^(y6AeV?x zilU?x;?09cm-47QNb*2<=S5Op{Sk^h;K370q(nk;DaxhfevKrzNMU~8*?zlc_neuX zz0A4HX{Sy*=lh-c?)UpS-I;T^-KM z%+&fhnD}Js>z{*QDQ5!u{QSi2?d>uJCdyYoASNA9ba!`)I-zWK_<+{e*F`U#(m*my z(yW66^8Eaa;8qLT-`@{q!We8z0Z8xh)6)~Gt*s5)aFY%Yfdvm06iCO%$9i@lsY?ln zkhs6UN8}>!^z<|&U{s^h0wMw2-rg#ZE-o(Ac!VDlgj*C~@a5$NEi5dc)zwwyb9i{j zw0d=Qg{rHo(aOpS%FoYdwsWJfZ*2FR-Bxitoa0iS*jNm72%tk8&(Kh zxygC_4zR3*8qnF<8E$NBv;@K=5L{ebjA3qKg<&c;u^U-H6B83SB_+i*95uj_k`nyz z@L&pqrd0b=j*$b@-`_6`j6Sfcswx@-Q<=WMz81FXGGB*OySE+ONn$ zEP4(a85z;e36Th`7ZeoW%gakvC``GDAASenhBnoJNI=)u*El^rT__YaxV*ePd~$L^ zje#drZ3G?-Lo6JRcY1ny(A(P^_#w9BnH7i$=-X}>0UuZC7cWe{BPV)7(J ze@ap)s@VHZEbCMYHG?>KRbsetState(Webrtc::VideoState::Inactive); + } return; } + const auto changing = isScreenSharing(); _videoDeviceId = _videoInputId; if (_videoOutgoing->state() != Webrtc::VideoState::Active) { _videoOutgoing->setState(Webrtc::VideoState::Active); } - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + if (!_videoCapture) { + return; + } + if (changing) { + _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + } } void GroupCall::switchToScreenSharing(const QString &uniqueId) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index a6773aa61..fb5e6cd17 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -244,7 +244,7 @@ public: void setCurrentAudioDevice(bool input, const QString &deviceId); void setCurrentVideoDevice(const QString &deviceId); bool isScreenSharing() const; - void switchToCamera(); + void toggleVideo(bool active); void switchToScreenSharing(const QString &uniqueId); void toggleMute(const Group::MuteRequest &data); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index cb0e2de1d..09821cce8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -685,30 +685,47 @@ void Panel::refreshLeftButton() { const auto share = _call->scheduleDate() && _peer->isBroadcast() && _peer->asChannel()->hasUsername(); - if ((share && _share) || (!share && _settings)) { + if ((share && _callShare) || (!share && _settings)) { return; } if (share) { _settings.destroy(); - _share.create(widget(), st::groupCallShare); - _share->setClickedCallback(_shareLinkCallback); - _share->setText(tr::lng_group_call_share_button()); + _callShare.create(widget(), st::groupCallShare); + _callShare->setClickedCallback(_callShareLinkCallback); + _callShare->setText(tr::lng_group_call_share_button()); } else { - _share.destroy(); + _callShare.destroy(); _settings.create(widget(), st::groupCallSettings); _settings->setClickedCallback([=] { - if (_call->isScreenSharing()) { - _call->switchToCamera(); - } else { - Ui::DesktopCapture::ChooseSource(this); - } - //_layerBg->showBox(Box(SettingsBox, _call)); + _layerBg->showBox(Box(SettingsBox, _call)); }); _settings->setText(tr::lng_group_call_settings()); } - const auto raw = _share ? _share.data() : _settings.data(); + const auto raw = _callShare ? _callShare.data() : _settings.data(); raw->show(); raw->setColorOverrides(_mute->colorOverrides()); + + if (!_video) { + _video.create(widget(), st::groupCallVideoSmall); + _video->show(); + _video->setClickedCallback([=] { + const auto sharing = _call->isScreenSharing(); + const auto active = (_call->outgoingVideoTrack()->state() + == Webrtc::VideoState::Active); + _call->toggleVideo(sharing || !active); + }); + _video->setText(tr::lng_group_call_video()); + _video->setColorOverrides(_mute->colorOverrides()); + } + if (!_screenShare) { + _screenShare.create(widget(), st::groupCallScreenShareSmall); + _screenShare->show(); + _screenShare->setClickedCallback([=] { + Ui::DesktopCapture::ChooseSource(this); + }); + _screenShare->setText(tr::lng_group_call_screen_share()); + _screenShare->setColorOverrides(_mute->colorOverrides()); + } } void Panel::initShareAction() { @@ -725,7 +742,7 @@ void Panel::initShareAction() { _peer, showBox, showToast); - _shareLinkCallback = [=, callback = std::move(shareLinkCallback)] { + _callShareLinkCallback = [=, callback = std::move(shareLinkCallback)] { if (_call->lookupReal()) { callback(); } @@ -911,7 +928,7 @@ void Panel::setupMembers() { _members->addMembersRequests( ) | rpl::start_with_next([=] { if (_peer->isBroadcast() && _peer->asChannel()->hasUsername()) { - _shareLinkCallback(); + _callShareLinkCallback(); } else { addMembers(); } @@ -1505,31 +1522,44 @@ bool Panel::updateMode() { } void Panel::updateControlsGeometry() { - if (widget()->size().isEmpty() || (!_settings && !_share)) { + if (widget()->size().isEmpty() || (!_settings && !_callShare)) { return; } if (_videoMode.current()) { _mute->setStyle(st::callMuteButtonSmall); const auto buttonsTop = widget()->height() - - st::groupCallButtonBottomSkip; - const auto muteSize = _mute->innerSize().width(); - const auto fullWidth = muteSize - + 2 * (_settings ? _settings : _share)->width() - + 2 * st::groupCallButtonSkip; - const auto leftButtonLeft = (widget()->width() - fullWidth) / 2; + - st::groupCallButtonBottomSkipSmall; const auto addSkip = st::callMuteButtonSmall.active.outerRadius; - _mute->moveInner({ leftButtonLeft + addSkip, buttonsTop + addSkip }); + const auto muteSize = _mute->innerSize().width() + 2 * addSkip; + const auto skip = (_video ? 1 : 2) * st::groupCallButtonSkipSmall; + const auto fullWidth = muteSize + + (_video ? _video->width() + skip : 0) + + (_screenShare ? _screenShare->width() + skip : 0) + + (_settings ? _settings : _callShare)->width() + skip + + _hangup->width() + skip; + auto left = (widget()->width() - fullWidth) / 2; + _mute->moveInner({ left + addSkip, buttonsTop + addSkip }); + left += muteSize + skip; + if (_video) { + _video->moveToLeft(left, buttonsTop); + left += _video->width() + skip; + } + if (_screenShare) { + _screenShare->moveToLeft(left, buttonsTop); + left += _video->width() + skip; + } if (_settings) { - _settings->moveToLeft( - (widget()->width() - _settings->width()) / 2, - buttonsTop); + _settings->setStyle(st::groupCallSettingsSmall); + _settings->moveToLeft(left, buttonsTop); + left += _settings->width() + skip; } - if (_share) { - _share->moveToLeft( - (widget()->width() - _share->width()) / 2, - buttonsTop); + if (_callShare) { + _callShare->setStyle(st::groupCallShareSmall); + _callShare->moveToLeft(left, buttonsTop); + left += _callShare->width() + skip; } - _hangup->moveToRight(leftButtonLeft, buttonsTop); + _hangup->setStyle(st::groupCallHangupSmall); + _hangup->moveToLeft(left, buttonsTop); } else { _mute->setStyle(st::callMuteButton); const auto muteTop = widget()->height() @@ -1538,16 +1568,19 @@ void Panel::updateControlsGeometry() { - st::groupCallButtonBottomSkip; const auto muteSize = _mute->innerSize().width(); const auto fullWidth = muteSize - + 2 * (_settings ? _settings : _share)->width() + + 2 * (_settings ? _settings : _callShare)->width() + 2 * st::groupCallButtonSkip; _mute->moveInner({ (widget()->width() - muteSize) / 2, muteTop }); const auto leftButtonLeft = (widget()->width() - fullWidth) / 2; if (_settings) { + _settings->setStyle(st::groupCallSettings); _settings->moveToLeft(leftButtonLeft, buttonsTop); } - if (_share) { - _share->moveToLeft(leftButtonLeft, buttonsTop); + if (_callShare) { + _callShare->setStyle(st::groupCallShare); + _callShare->moveToLeft(leftButtonLeft, buttonsTop); } + _hangup->setStyle(st::groupCallHangup); _hangup->moveToRight(leftButtonLeft, buttonsTop); } updateMembersGeometry(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index a963fbf62..c9b535424 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -147,12 +147,12 @@ private: rpl::variable _videoMode; object_ptr _settings = { nullptr }; - object_ptr _share = { nullptr }; + object_ptr _callShare = { nullptr }; object_ptr _video = { nullptr }; - object_ptr _shareScreen = { nullptr }; + object_ptr _screenShare = { nullptr }; std::unique_ptr _mute; object_ptr _hangup; - Fn _shareLinkCallback; + Fn _callShareLinkCallback; rpl::lifetime _peerLifetime; diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp index 8a5b6ad0a..76b1d6b95 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp @@ -175,11 +175,9 @@ public: int additionalHeight, const style::FlatLabel &st = st::defaultFlatLabel); - int height() const; + int contentHeight() const; private: - int realHeight() const; - void setText(const QString &text); const style::FlatLabel &_st; @@ -220,9 +218,9 @@ AnimatedLabel::AnimatedLabel( p.setPen(_st.textFg); p.setTextPalette(_st.palette); - const auto textHeight = height(); - const auto diffHeight = realHeight() - textHeight; - const auto center = (diffHeight) / 2; + const auto textHeight = contentHeight(); + const auto diffHeight = height() - textHeight; + const auto center = diffHeight / 2; p.setOpacity(1. - progress); if (p.opacity()) { @@ -246,14 +244,10 @@ AnimatedLabel::AnimatedLabel( }, lifetime()); } -int AnimatedLabel::height() const { +int AnimatedLabel::contentHeight() const { return _st.style.font->height; } -int AnimatedLabel::realHeight() const { - return RpWidget::height(); -} - void AnimatedLabel::setText(const QString &text) { if (_text.toString() == text) { return; @@ -264,7 +258,9 @@ void AnimatedLabel::setText(const QString &text) { const auto width = std::max( _st.style.font->width(_text.toString()), _st.style.font->width(_previousText.toString())); - resize(width + _additionalHeight, height() + _additionalHeight * 2); + resize( + width + _additionalHeight, + contentHeight() + _additionalHeight * 2); _animation.stop(); _animation.start([=] { update(); }, 0., 1., _duration); @@ -933,25 +929,31 @@ void CallMuteButton::updateLabelsGeometry() { void CallMuteButton::updateLabelGeometry(QRect my, QSize size) { const auto skip = _st->sublabelSkip + _st->labelsSkip; + const auto contentHeight = _label->contentHeight(); + const auto contentTop = my.y() + my.height() - contentHeight - skip; _label->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - size.height() - skip, + contentTop - (size.height() - contentHeight) / 2, my.width()); } void CallMuteButton::updateCenterLabelGeometry(QRect my, QSize size) { const auto skip = (_st->sublabelSkip / 2) + _st->labelsSkip; + const auto contentHeight = _centerLabel->contentHeight(); + const auto contentTop = my.y() + my.height() - contentHeight - skip; _centerLabel->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - size.height() - skip, + contentTop - (size.height() - contentHeight) / 2, my.width()); } void CallMuteButton::updateSublabelGeometry(QRect my, QSize size) { const auto skip = _st->labelsSkip; + const auto contentHeight = _sublabel->contentHeight(); + const auto contentTop = my.y() + my.height() - contentHeight - skip; _sublabel->moveToLeft( my.x() + (my.width() - size.width()) / 2 + _labelShakeShift, - my.y() + my.height() - size.height() - skip, + contentTop - (size.height() - contentHeight) / 2, my.width()); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 14c67cf72..3c1866f52 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 14c67cf724a572186455a8c0639f037ae26cc762 +Subproject commit 3c1866f52d88f9430341f9a9f1ed3dbbb3b794ea From 7b3b5a146398be3325e784cd63702dc342dbe652 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Apr 2021 17:34:05 +0400 Subject: [PATCH 082/477] Use 'Screencast' video content type. --- Telegram/SourceFiles/calls/group/calls_group_call.cpp | 6 ++---- Telegram/ThirdParty/tgcalls | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 190b574ac..7e2e704bf 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -407,9 +407,7 @@ bool GroupCall::isScreenSharing() const { void GroupCall::toggleVideo(bool active) { if (!active) { - if (!isScreenSharing()) { - _videoOutgoing->setState(Webrtc::VideoState::Inactive); - } + _videoOutgoing->setState(Webrtc::VideoState::Inactive); return; } const auto changing = isScreenSharing(); @@ -1581,7 +1579,7 @@ void GroupCall::ensureControllerCreated() { }); return result; }, - .enableVideo = true, + .videoContentType = tgcalls::VideoContentType::Screencast, }; if (Logs::DebugEnabled()) { auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 4a944bc29..909612ca6 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 4a944bc29ec308556471a1533874a68dd95a9ba3 +Subproject commit 909612ca66cef26deffa5dc2dcd964ddde1b4c11 From dac9017df167e097c88fa35692e2a7382b77cf9f Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Apr 2021 18:06:56 +0400 Subject: [PATCH 083/477] Improve video chat controls layout. --- Telegram/SourceFiles/calls/calls.style | 5 ++ .../calls/group/calls_group_panel.cpp | 81 +++++++++++++++++-- .../calls/group/calls_group_panel.h | 3 + .../ui/controls/call_mute_button.cpp | 9 +++ 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index b0ad45ca9..128f5e35a 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -678,6 +678,7 @@ groupCallShareBoxList: PeerList(groupCallMembersList) { groupCallMembersTop: 51px; groupCallTitleTop: 8px; groupCallSubtitleTop: 26px; +groupCallWideVideoTop: 24px; groupCallMembersMargin: margins(16px, 16px, 16px, 28px); groupCallAddMember: SettingsButton(defaultSettingsButton) { @@ -832,6 +833,10 @@ groupCallButtonSkip: 43px; groupCallButtonSkipSmall: 4px; groupCallButtonBottomSkip: 145px; groupCallButtonBottomSkipSmall: 95px; +groupCallButtonBottomSkipWide: 89px; +groupCallMembersBottomSkipSmall: 72px; +groupCallControlsBackMargin: margins(2px, 2px, 2px, 2px); +groupCallControlsBackRadius: 12px; groupCallMuteBottomSkip: 160px; groupCallTopBarUserpics: GroupCallUserpics { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 09821cce8..10f2324ea 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" #include "ui/toasts/common_toasts.h" +#include "ui/round_rect.h" #include "ui/special_buttons.h" #include "info/profile/info_profile_values.h" // Info::Profile::Value. #include "core/application.h" @@ -63,6 +64,7 @@ constexpr auto kSpacePushToTalkDelay = crl::time(250); constexpr auto kRecordingAnimationDuration = crl::time(1200); constexpr auto kRecordingOpacity = 0.6; constexpr auto kStartNoConfirmation = TimeId(10); +constexpr auto kControlsBackgroundOpacity = 0.8; class InviteController final : public ParticipantsBoxController { public: @@ -706,7 +708,10 @@ void Panel::refreshLeftButton() { raw->setColorOverrides(_mute->colorOverrides()); if (!_video) { - _video.create(widget(), st::groupCallVideoSmall); + _video.create( + widget(), + st::groupCallVideoSmall, + &st::groupCallVideoActiveSmall); _video->show(); _video->setClickedCallback([=] { const auto sharing = _call->isScreenSharing(); @@ -937,10 +942,31 @@ void Panel::setupMembers() { setupPinnedVideo(); } +void Panel::raiseControls() { + if (_controlsBackground) { + _controlsBackground->raise(); + } + const auto buttons = { + &_settings, + &_callShare, + &_screenShare, + &_video, + &_hangup + }; + for (const auto button : buttons) { + if (const auto raw = button->data()) { + raw->raise(); + } + } + _mute->raise(); +} + void Panel::setupPinnedVideo() { _pinnedVideo.create(widget()); _pinnedVideo->setVisible(_mode == PanelMode::Wide); + raiseControls(); + rpl::combine( _pinnedVideo->shownValue(), _call->videoLargeTrackValue() @@ -1517,10 +1543,36 @@ bool Panel::updateMode() { _members->setMode(mode); } _pinnedVideo->setVisible(mode == PanelMode::Wide); + refreshControlsBackground(); updateControlsGeometry(); return true; } +void Panel::refreshControlsBackground() { + if (_mode != PanelMode::Wide) { + _controlsBackground.destroy(); + } else if (_controlsBackground) { + return; + } + _controlsBackground.create(widget()); + _controlsBackground->show(); + auto &lifetime = _controlsBackground->lifetime(); + const auto color = lifetime.make_state([] { + auto result = st::groupCallBg->c; + result.setAlphaF(kControlsBackgroundOpacity); + return result; + }); + const auto corners = lifetime.make_state( + st::groupCallControlsBackRadius, + color->color()); + _controlsBackground->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(_controlsBackground.data()); + corners->paint(p, _controlsBackground->rect()); + }, lifetime); + raiseControls(); +} + void Panel::updateControlsGeometry() { if (widget()->size().isEmpty() || (!_settings && !_callShare)) { return; @@ -1560,6 +1612,16 @@ void Panel::updateControlsGeometry() { } _hangup->setStyle(st::groupCallHangupSmall); _hangup->moveToLeft(left, buttonsTop); + left += _hangup->width(); + if (_controlsBackground) { + const auto rect = QRect( + left - fullWidth, + buttonsTop, + fullWidth, + _hangup->height()); + _controlsBackground->setGeometry( + rect.marginsAdded(st::groupCallControlsBackMargin)); + } } else { _mute->setStyle(st::callMuteButton); const auto muteTop = widget()->height() @@ -1614,11 +1676,6 @@ void Panel::updateMembersGeometry() { return; } const auto desiredHeight = _members->desiredHeight(); - const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip; - const auto membersTop = st::groupCallMembersTop; - const auto availableHeight = muteTop - - membersTop - - st::groupCallMembersMargin.bottom(); if (_mode == PanelMode::Wide) { _members->setGeometry( st::groupCallNarrowSkip, @@ -1627,12 +1684,20 @@ void Panel::updateMembersGeometry() { std::min(desiredHeight, widget()->height())); const auto pinnedLeft = st::groupCallNarrowSkip * 2 + st::groupCallNarrowSize.width(); + const auto pinnedTop = st::groupCallWideVideoTop; _pinnedVideo->setGeometry( pinnedLeft, - membersTop, + pinnedTop, widget()->width() - pinnedLeft - st::groupCallNarrowSkip, - availableHeight); + widget()->height() - pinnedTop - st::groupCallNarrowSkip); } else { + const auto membersBottom = _videoMode.current() + ? (widget()->height() - st::groupCallMembersBottomSkipSmall) + : (widget()->height() - st::groupCallMuteBottomSkip); + const auto membersTop = st::groupCallMembersTop; + const auto availableHeight = membersBottom + - st::groupCallMembersMargin.bottom() + - membersTop; const auto membersWidthAvailable = widget()->width() - st::groupCallMembersMargin.left() - st::groupCallMembersMargin.right(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index c9b535424..ed14ca4c3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -90,10 +90,12 @@ private: bool handleClose(); void startScheduledNow(); + void raiseControls(); bool updateMode(); void updateControlsGeometry(); void updateMembersGeometry(); + void refreshControlsBackground(); void showControls(); void refreshLeftButton(); @@ -146,6 +148,7 @@ private: ChooseJoinAsProcess _joinAsProcess; rpl::variable _videoMode; + object_ptr _controlsBackground = { nullptr }; object_ptr _settings = { nullptr }; object_ptr _callShare = { nullptr }; object_ptr _video = { nullptr }; diff --git a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp index 76b1d6b95..0bcfbdb9f 100644 --- a/Telegram/SourceFiles/ui/controls/call_mute_button.cpp +++ b/Telegram/SourceFiles/ui/controls/call_mute_button.cpp @@ -1064,6 +1064,9 @@ void CallMuteButton::moveInner(QPoint position) { } void CallMuteButton::setVisible(bool visible) { + _centerLabel->setVisible(visible); + _label->setVisible(visible); + _sublabel->setVisible(visible); _content->setVisible(visible); _blobs->setVisible(visible); } @@ -1071,9 +1074,15 @@ void CallMuteButton::setVisible(bool visible) { void CallMuteButton::raise() { _blobs->raise(); _content->raise(); + _centerLabel->raise(); + _label->raise(); + _sublabel->raise(); } void CallMuteButton::lower() { + _centerLabel->lower(); + _label->lower(); + _sublabel->lower(); _content->lower(); _blobs->lower(); } From 1a784fc678f3d0f1c2f25fb6e3af697c9e9ac36f Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Apr 2021 19:13:45 +0400 Subject: [PATCH 084/477] Track video_muted from API. --- Telegram/SourceFiles/calls/calls.style | 1 + .../calls/group/calls_group_call.cpp | 154 ++++++++++++------ .../calls/group/calls_group_call.h | 7 +- Telegram/SourceFiles/data/data_group_call.cpp | 1 + Telegram/SourceFiles/data/data_group_call.h | 1 + 5 files changed, 113 insertions(+), 51 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 128f5e35a..6a0222abc 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -827,6 +827,7 @@ groupCallVideoActiveSmall: CallButton(groupCallVideoSmall) { groupCallScreenShareSmall: CallButton(groupCallShareSmall) { button: IconButton(groupCallSettingsInner) { icon: icon {{ "calls/calls_present", groupCallIconFg }}; + height: 76px; } } groupCallButtonSkip: 43px; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 7e2e704bf..1975216d5 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -407,13 +407,17 @@ bool GroupCall::isScreenSharing() const { void GroupCall::toggleVideo(bool active) { if (!active) { - _videoOutgoing->setState(Webrtc::VideoState::Inactive); + if (_videoOutgoing->state() != Webrtc::VideoState::Inactive) { + _videoOutgoing->setState(Webrtc::VideoState::Inactive); + sendSelfUpdate(SendUpdateType::VideoMuted); + } return; } const auto changing = isScreenSharing(); _videoDeviceId = _videoInputId; if (_videoOutgoing->state() != Webrtc::VideoState::Active) { _videoOutgoing->setState(Webrtc::VideoState::Active); + sendSelfUpdate(SendUpdateType::VideoMuted); } if (!_videoCapture) { return; @@ -430,6 +434,7 @@ void GroupCall::switchToScreenSharing(const QString &uniqueId) { _videoDeviceId = uniqueId; if (_videoOutgoing->state() != Webrtc::VideoState::Active) { _videoOutgoing->setState(Webrtc::VideoState::Active); + sendSelfUpdate(SendUpdateType::VideoMuted); } _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } @@ -451,6 +456,43 @@ void GroupCall::subscribeToReal(not_null real) { using Update = Data::GroupCall::ParticipantUpdate; real->participantUpdated( ) | rpl::start_with_next([=](const Update &data) { + auto newLarge = _videoStreamLarge.current(); + auto updateAsNotStreams = uint32(0); + const auto guard = gsl::finally([&] { + if (!newLarge) { + newLarge = chooseLargeVideoSsrc(); + } + if (_videoStreamLarge.current() != newLarge) { + _videoStreamLarge = newLarge; + } + if (updateAsNotStreams) { + _streamsVideoUpdated.fire({ updateAsNotStreams, false }); + } + }); + + const auto wasVideoMutedSsrc = (data.was && data.was->videoMuted) + ? data.was->ssrc + : 0; + const auto nowVideoMutedSsrc = (data.now && data.now->videoMuted) + ? data.now->ssrc + : 0; + if (wasVideoMutedSsrc != nowVideoMutedSsrc) { + if (wasVideoMutedSsrc + && _videoMuted.remove(wasVideoMutedSsrc) + && _videoStreamSsrcs.contains(wasVideoMutedSsrc) + && data.now + && data.now->ssrc == wasVideoMutedSsrc) { + _streamsVideoUpdated.fire({ wasVideoMutedSsrc, true }); + } + if (nowVideoMutedSsrc + && _videoMuted.emplace(nowVideoMutedSsrc).second + && _videoStreamSsrcs.contains(nowVideoMutedSsrc)) { + updateAsNotStreams = nowVideoMutedSsrc; + if (newLarge == nowVideoMutedSsrc) { + newLarge = 0; + } + } + } const auto nowSpeaking = data.now && data.now->speaking; const auto nowSounding = data.now && data.now->sounding; const auto wasSpeaking = data.was && data.was->speaking; @@ -460,15 +502,13 @@ void GroupCall::subscribeToReal(not_null real) { } else if (_videoStreamPinned) { return; } - const auto videoLargeSsrc = _videoStreamLarge.current(); const auto &participants = real->participants(); if ((wasSpeaking || wasSounding) - && (data.was->ssrc == videoLargeSsrc)) { + && (data.was->ssrc == newLarge)) { auto bestWithVideoSsrc = uint32(0); for (const auto &participant : participants) { if (!participant.sounding - || !participant.ssrc - || !_videoStreamSsrcs.contains(participant.ssrc)) { + || !streamsVideo(participant.ssrc)) { continue; } if (participant.speaking) { @@ -479,19 +519,19 @@ void GroupCall::subscribeToReal(not_null real) { } } if (bestWithVideoSsrc) { - _videoStreamLarge = bestWithVideoSsrc; + newLarge = bestWithVideoSsrc; } } else if ((nowSpeaking || nowSounding) - && (data.now->ssrc != videoLargeSsrc) - && _videoStreamSsrcs.contains(data.now->ssrc)) { + && (data.now->ssrc != newLarge) + && streamsVideo(data.now->ssrc)) { const auto i = ranges::find( participants, - videoLargeSsrc, + newLarge, &Data::GroupCallParticipant::ssrc); const auto speaking = (i != end(participants)) && i->speaking; const auto sounding = (i != end(participants)) && i->sounding; if ((nowSpeaking && !speaking) || (nowSounding && !sounding)) { - _videoStreamLarge = data.now->ssrc; + newLarge = data.now->ssrc; } } }, _lifetime); @@ -840,6 +880,7 @@ void GroupCall::rejoin(not_null as) { _peer->session().api().applyUpdates(updates); applyQueuedSelfUpdates(); checkFirstTimeJoined(); + sendSelfUpdate(SendUpdateType::VideoMuted); }).fail([=](const MTP::Error &error) { const auto type = error.type(); LOG(("Call Error: Could not join, error: %1").arg(type)); @@ -895,6 +936,8 @@ void GroupCall::applyMeInCallLocally() { : Group::kDefaultVolume; const auto canSelfUnmute = (muted() != MuteState::ForceMuted) && (muted() != MuteState::RaisedHand); + const auto videoMuted = (_videoOutgoing->state() + != Webrtc::VideoState::Active); const auto raisedHandRating = (muted() != MuteState::RaisedHand) ? uint64(0) : (i != end(participants)) @@ -907,6 +950,7 @@ void GroupCall::applyMeInCallLocally() { | Flag::f_volume // Without flag the volume is reset to 100%. | Flag::f_volume_by_admin // Self volume can only be set by admin. | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) + | (videoMuted ? Flag::f_video_muted : Flag(0)) | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)); call->applyLocalUpdate( MTP_updateGroupCallParticipants( @@ -951,6 +995,7 @@ void GroupCall::applyParticipantLocally( | (isMuted ? Flag::f_muted : Flag(0)) | (isMutedByYou ? Flag::f_muted_by_you : Flag(0)) | (participantPeer == _joinAs ? Flag::f_self : Flag(0)) + | (participant->videoMuted ? Flag::f_video_muted : Flag(0)) | (participant->raisedHandRating ? Flag::f_raise_hand_rating : Flag(0)); @@ -1725,65 +1770,72 @@ void GroupCall::requestParticipantsInformation( } void GroupCall::setVideoStreams(const std::vector &ssrcs) { - const auto real = lookupReal(); const auto large = _videoStreamLarge.current(); auto newLarge = large; if (large && !ranges::contains(ssrcs, large)) { newLarge = 0; _videoStreamPinned = 0; } - auto lastSpokeVoiceSsrc = uint32(0); - auto lastSpokeAnythingSsrc = uint32(0); auto removed = _videoStreamSsrcs; for (const auto ssrc : ssrcs) { const auto i = removed.find(ssrc); + const auto videoMuted = _videoMuted.contains(ssrc); if (i != end(removed)) { removed.erase(i); } else { _videoStreamSsrcs.emplace(ssrc); - _streamsVideoUpdated.fire({ ssrc, true }); - } - if (!newLarge && real) { - const auto &participants = real->participants(); - const auto i = ranges::find( - participants, - ssrc, - &Data::GroupCallParticipant::ssrc); - if (i != end(participants)) { - if (!lastSpokeVoiceSsrc && i->speaking) { - lastSpokeVoiceSsrc = ssrc; - } - if (!lastSpokeAnythingSsrc && i->sounding) { - lastSpokeAnythingSsrc = ssrc; - } + if (!videoMuted) { + _streamsVideoUpdated.fire({ ssrc, true }); } } } - if (!newLarge && real) { - const auto find = [&] { - const auto &participants = real->participants(); - for (const auto ssrc : ssrcs) { - const auto i = ranges::find( - participants, - ssrc, - &Data::GroupCallParticipant::ssrc); - if (i != end(participants)) { - return ssrc; - } - } - return std::uint32_t(0); - }; - _videoStreamLarge = lastSpokeVoiceSsrc - ? lastSpokeVoiceSsrc - : lastSpokeAnythingSsrc - ? lastSpokeAnythingSsrc - : find(); + if (!newLarge) { + _videoStreamLarge = chooseLargeVideoSsrc(); } for (const auto ssrc : removed) { - _streamsVideoUpdated.fire({ ssrc, false }); + if (!_videoMuted.contains(ssrc)) { + _streamsVideoUpdated.fire({ ssrc, false }); + } } } +uint32 GroupCall::chooseLargeVideoSsrc() const { + const auto real = lookupReal(); + if (!real) { + return 0; + } + auto anySsrc = uint32(0); + auto lastSpokeVoiceSsrc = uint32(0); + auto lastSpokeAnythingSsrc = uint32(0); + const auto &participants = real->participants(); + for (const auto ssrc : _videoStreamSsrcs) { + if (_videoMuted.contains(ssrc)) { + continue; + } + const auto &participants = real->participants(); + const auto i = ranges::find( + participants, + ssrc, + &Data::GroupCallParticipant::ssrc); + if (i != end(participants)) { + if (!lastSpokeVoiceSsrc && i->speaking) { + lastSpokeVoiceSsrc = ssrc; + } + if (!lastSpokeAnythingSsrc && i->sounding) { + lastSpokeAnythingSsrc = ssrc; + } + if (!anySsrc) { + anySsrc = ssrc; + } + } + } + return lastSpokeVoiceSsrc + ? lastSpokeVoiceSsrc + : lastSpokeAnythingSsrc + ? lastSpokeAnythingSsrc + : anySsrc; +} + void GroupCall::updateInstanceMuteState() { Expects(_instance != nullptr); @@ -2006,13 +2058,15 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { _updateMuteRequestId = _api.request(MTPphone_EditGroupCallParticipant( MTP_flags((type == SendUpdateType::RaiseHand) ? Flag::f_raise_hand + : (type == SendUpdateType::VideoMuted) + ? Flag::f_video_muted : Flag::f_muted), inputCall(), _joinAs->input, MTP_bool(muted() != MuteState::Active), MTP_int(100000), // volume MTP_bool(muted() == MuteState::RaisedHand), - MTPBool() // video_muted + MTP_bool(_videoOutgoing->state() == Webrtc::VideoState::Active) )).done([=](const MTPUpdates &result) { _updateMuteRequestId = 0; _peer->session().api().applyUpdates(result); @@ -2027,7 +2081,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { } void GroupCall::pinVideoStream(uint32 ssrc) { - if (!ssrc || _videoStreamSsrcs.contains(ssrc)) { + if (!ssrc || streamsVideo(ssrc)) { _videoStreamPinned = ssrc; if (ssrc) { _videoStreamLarge = ssrc; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index fb5e6cd17..4ff1f7457 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -211,7 +211,9 @@ public: return _streamsVideoUpdated.events(); } [[nodiscard]] bool streamsVideo(uint32 ssrc) const { - return _videoStreamSsrcs.contains(ssrc); + return ssrc + && _videoStreamSsrcs.contains(ssrc) + && !_videoMuted.contains(ssrc); } [[nodiscard]] uint32 videoStreamPinned() const { return _videoStreamPinned; @@ -290,6 +292,7 @@ private: enum class SendUpdateType { Mute, RaiseHand, + VideoMuted, }; void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data); @@ -335,6 +338,7 @@ private: void addPreparedParticipants(); void addPreparedParticipantsDelayed(); void setVideoStreams(const std::vector &ssrcs); + [[nodiscard]] uint32 chooseLargeVideoSsrc() const; void editParticipant( not_null participantPeer, @@ -398,6 +402,7 @@ private: rpl::event_stream _levelUpdates; rpl::event_stream _streamsVideoUpdated; base::flat_set _videoStreamSsrcs; + base::flat_set _videoMuted; rpl::variable _videoStreamLarge = 0; uint32 _videoStreamPinned = 0; std::unique_ptr _videoLargeTrackWrap; diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 9862936b5..d6895e20d 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -568,6 +568,7 @@ void GroupCall::applyParticipantsSlice( .muted = data.is_muted(), .mutedByMe = mutedByMe, .canSelfUnmute = canSelfUnmute, + .videoMuted = data.is_video_muted(), .onlyMinLoaded = onlyMinLoaded, }; if (i == end(_participants)) { diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 747b43b50..4a7a5965e 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -38,6 +38,7 @@ struct GroupCallParticipant { bool muted = false; bool mutedByMe = false; bool canSelfUnmute = false; + bool videoMuted = true; bool onlyMinLoaded = false; }; From 75f220c3d99ebee26aa228e0003d389c83ce832d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Apr 2021 19:35:34 +0400 Subject: [PATCH 085/477] Fix build for Linux. --- Telegram/build/docker/centos_env/Dockerfile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Telegram/build/docker/centos_env/Dockerfile b/Telegram/build/docker/centos_env/Dockerfile index 5fc8fdf11..0d8aa8f02 100644 --- a/Telegram/build/docker/centos_env/Dockerfile +++ b/Telegram/build/docker/centos_env/Dockerfile @@ -112,6 +112,22 @@ RUN make DESTDIR="$LibrariesPath/opus-cache" install WORKDIR .. RUN rm -rf opus +FROM builder AS rnnoise +RUN git clone -b master --depth=1 $GIT/desktop-app/rnnoise + +WORKDIR rnnoise +RUN cmake3 -B build . \ + -DCMAKE_BUILD_TYPE=Release + +RUN cmake3 --build build -j$(nproc) +RUN mkdir -p "$LibrariesPath/rnnoise-cache/usr/local/include" +RUN cp "include/rnnoise.h" "$LibrariesPath/rnnoise-cache/usr/local/include/" +RUN mkdir -p "$LibrariesPath/rnnoise-cache/usr/local/lib" +RUN cp "build/librnnoise.a" "$LibrariesPath/rnnoise-cache/usr/local/lib/" + +WORKDIR .. +RUN rm -rf rnnoise + FROM builder AS xcb-proto RUN git clone -b xcb-proto-1.14 --depth=1 https://gitlab.freedesktop.org/xorg/proto/xcbproto.git @@ -691,6 +707,7 @@ COPY --from=xz ${LibrariesPath}/xz-cache / COPY --from=libproxy ${LibrariesPath}/libproxy-cache / COPY --from=mozjpeg ${LibrariesPath}/mozjpeg-cache / COPY --from=opus ${LibrariesPath}/opus-cache / +COPY --from=rnnoise ${LibrariesPath}/rnnoise-cache / COPY --from=xcb ${LibrariesPath}/xcb-cache / COPY --from=xcb-wm ${LibrariesPath}/xcb-wm-cache / COPY --from=xcb-util ${LibrariesPath}/xcb-util-cache / From 36888f844f88547aadf59ac56a262c6c68f5db02 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Apr 2021 19:35:47 +0400 Subject: [PATCH 086/477] Disable (crashing) capturer on Linux. --- Telegram/SourceFiles/calls/group/calls_group_panel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 10f2324ea..75602a10f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -726,7 +726,9 @@ void Panel::refreshLeftButton() { _screenShare.create(widget(), st::groupCallScreenShareSmall); _screenShare->show(); _screenShare->setClickedCallback([=] { +#ifndef Q_OS_LINUX Ui::DesktopCapture::ChooseSource(this); +#endif // Q_OS_LINUX }); _screenShare->setText(tr::lng_group_call_screen_share()); _screenShare->setColorOverrides(_mute->colorOverrides()); From 8342b2d275279cb359f4527c0390285863576845 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 27 Apr 2021 21:31:16 +0400 Subject: [PATCH 087/477] Fix bug with video_muted flag. --- Telegram/SourceFiles/calls/group/calls_group_call.cpp | 2 +- Telegram/SourceFiles/calls/group/calls_group_members.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 1975216d5..4e90510c7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -2066,7 +2066,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { MTP_bool(muted() != MuteState::Active), MTP_int(100000), // volume MTP_bool(muted() == MuteState::RaisedHand), - MTP_bool(_videoOutgoing->state() == Webrtc::VideoState::Active) + MTP_bool(_videoOutgoing->state() != Webrtc::VideoState::Active) )).done([=](const MTPUpdates &result) { _updateMuteRequestId = 0; _peer->session().api().applyUpdates(result); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index c556bec5e..aadf05ecc 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -690,7 +690,8 @@ bool Row::paintVideo(Painter &p, int x, int y, int size, PanelMode mode) { _videoTrackShown->markFrameShown(); }); const auto videoSize = _videoTrackShown->frameSize(); - if (videoSize.isEmpty()) { + if (videoSize.isEmpty() + || _videoTrackShown->state() != Webrtc::VideoState::Active) { return false; } const auto resize = (videoSize.width() > videoSize.height()) From b3fcb4ef36a976194ab4aa3a67e9481c03457775 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 29 Apr 2021 12:36:04 +0400 Subject: [PATCH 088/477] Allow 'mute for me' of muted by admin participants. --- Telegram/SourceFiles/calls/group/calls_group_members.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index aadf05ecc..ce7908782 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -2010,10 +2010,7 @@ void MembersController::addMuteActionsToContextMenu( || isMe(participantPeer) || (muteState == Row::State::Inactive && participantIsCallAdmin - && _peer->canManageGroupCall()) - || (isMuted - && !_peer->canManageGroupCall() - && muteState != Row::State::MutedByMe)) { + && _peer->canManageGroupCall())) { return nullptr; } auto callback = [=] { From 69ceed5bbce5f805c5bc5385aa24ea398f4aa280 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 29 Apr 2021 13:56:09 +0400 Subject: [PATCH 089/477] Update API scheme to layer 129. --- Telegram/Resources/tl/api.tl | 11 +- Telegram/SourceFiles/api/api_updates.cpp | 1 + Telegram/SourceFiles/calls/calls_instance.cpp | 7 + .../calls/group/calls_group_call.cpp | 234 ++++++++++-------- .../calls/group/calls_group_call.h | 5 + Telegram/SourceFiles/data/data_group_call.cpp | 6 +- 6 files changed, 159 insertions(+), 105 deletions(-) diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 720ac01bb..8da6f836a 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -375,6 +375,7 @@ updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update; updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update; updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update; updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update; +updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1204,11 +1205,11 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#c95c6654 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall; +groupCall#653dbaad flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_muted:flags.14?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant; +groupCallParticipant#a8ba51a7 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?DataJSON presentation:flags.14?DataJSON = GroupCallParticipant; phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; @@ -1624,7 +1625,7 @@ phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates; phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; -phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool; +phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector = Vector; phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates; phone.editGroupCallParticipant#aec610e4 flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_muted:flags.3?Bool = Updates; phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates; @@ -1633,6 +1634,8 @@ phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:I phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates; phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates; phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool; +phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates; +phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1649,4 +1652,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 128 +// LAYER 129 diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 021811c23..d3feabee8 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -1878,6 +1878,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updatePhoneCall: case mtpc_updatePhoneCallSignalingData: case mtpc_updateGroupCallParticipants: + case mtpc_updateGroupCallConnection: case mtpc_updateGroupCall: { Core::App().calls().handleUpdate(&session(), update); } break; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 3722dad09..be7387298 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -391,6 +391,8 @@ void Instance::handleUpdate( handleSignalingData(session, data); }, [&](const MTPDupdateGroupCall &data) { handleGroupCallUpdate(session, update); + }, [&](const MTPDupdateGroupCallConnection &data) { + handleGroupCallUpdate(session, update); }, [&](const MTPDupdateGroupCallParticipants &data) { handleGroupCallUpdate(session, update); }, [](const auto &) { @@ -482,10 +484,15 @@ void Instance::handleGroupCallUpdate( && (&_currentGroupCall->peer()->session() == session)) { update.match([&](const MTPDupdateGroupCall &data) { _currentGroupCall->handlePossibleCreateOrJoinResponse(data); + }, [&](const MTPDupdateGroupCallConnection &data) { + _currentGroupCall->handlePossibleCreateOrJoinResponse(data); }, [](const auto &) { }); } + if (update.type() == mtpc_updateGroupCallConnection) { + return; + } const auto callId = update.match([](const MTPDupdateGroupCall &data) { return data.vcall().match([](const auto &data) { return data.vid().v; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 4e90510c7..8aed5a0ba 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -76,6 +76,12 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); return msgId / double(1ULL << 32); } +[[nodiscard]] std::string ReadJsonString( + const QJsonObject &object, + const char *key) { + return object.value(key).toString().toStdString(); +}; + } // namespace struct VideoParams { @@ -177,13 +183,8 @@ std::shared_ptr ParseVideoParams( return data; } - const auto readString = []( - const QJsonObject &object, - const char *key) { - return object.value(key).toString().toStdString(); - }; const auto object = document.object(); - data->description.endpointId = readString(object, "endpoint"); + data->description.endpointId = ReadJsonString(object, "endpoint"); const auto ssrcGroups = object.value("ssrc-groups").toArray(); data->description.videoSourceGroups.reserve(ssrcGroups.size()); @@ -201,58 +202,11 @@ std::shared_ptr ParseVideoParams( } data->description.videoSourceGroups.push_back({ .ssrcs = std::move(sources), - .semantics = readString(inner, "semantics"), - }); - } - - const auto payloadTypes = object.value("payload-types").toArray(); - data->description.videoPayloadTypes.reserve(payloadTypes.size()); - for (const auto &value : payloadTypes) { - const auto inner = value.toObject(); - - auto types = std::vector(); - { - const auto list = inner.value("rtcp-fbs").toArray(); - types.reserve(list.size()); - for (const auto &type : list) { - const auto inside = type.toObject(); - types.push_back({ - .type = readString(inside, "type"), - .subtype = readString(inside, "subtype"), - }); - } - } - auto parameters = std::vector>(); - { - const auto list = inner.value("parameters").toObject(); - parameters.reserve(list.size()); - for (auto i = list.begin(); i != list.end(); ++i) { - parameters.push_back({ - i.key().toStdString(), - i.value().toString().toStdString(), - }); - } - } - data->description.videoPayloadTypes.push_back({ - .id = uint32_t(inner.value("id").toDouble()), - .name = readString(inner, "name"), - .clockrate = uint32_t(inner.value("clockrate").toDouble()), - .channels = uint32_t(inner.value("channels").toDouble()), - .feedbackTypes = std::move(types), - .parameters = std::move(parameters), - }); - } - - const auto extensionMap = object.value("rtp-hdrexts").toArray(); - data->description.videoExtensionMap.reserve(extensionMap.size()); - for (const auto &extension : extensionMap) { - const auto inner = extension.toObject(); - data->description.videoExtensionMap.push_back({ - uint32_t(inner.value("id").toDouble()), - readString(inner, "uri"), + .semantics = ReadJsonString(inner, "semantics"), }); } + // videoPayloadTypes and videoExtensionMap will be in _commonVideoFields. return data; } @@ -950,7 +904,7 @@ void GroupCall::applyMeInCallLocally() { | Flag::f_volume // Without flag the volume is reset to 100%. | Flag::f_volume_by_admin // Self volume can only be set by admin. | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) - | (videoMuted ? Flag::f_video_muted : Flag(0)) + //| (videoMuted ? Flag(0) : Flag::f_video) | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)); call->applyLocalUpdate( MTP_updateGroupCallParticipants( @@ -966,7 +920,8 @@ void GroupCall::applyMeInCallLocally() { MTP_int(volume), MTPstring(), // Don't update about text in local updates. MTP_long(raisedHandRating), - MTPDataJSON())), + MTPDataJSON(), // video + MTPDataJSON())), // presentation MTP_int(0)).c_updateGroupCallParticipants()); } @@ -995,7 +950,7 @@ void GroupCall::applyParticipantLocally( | (isMuted ? Flag::f_muted : Flag(0)) | (isMutedByYou ? Flag::f_muted_by_you : Flag(0)) | (participantPeer == _joinAs ? Flag::f_self : Flag(0)) - | (participant->videoMuted ? Flag::f_video_muted : Flag(0)) + //| (participant->videoMuted ? Flag(0) : Flag::f_video) | (participant->raisedHandRating ? Flag::f_raise_hand_rating : Flag(0)); @@ -1013,7 +968,8 @@ void GroupCall::applyParticipantLocally( MTP_int(volume.value_or(participant->volume)), MTPstring(), // Don't update about text in local updates. MTP_long(participant->raisedHandRating), - MTPDataJSON())), + MTPDataJSON(), // video + MTPDataJSON())), // presentation MTP_int(0)).c_updateGroupCallParticipants()); } @@ -1195,13 +1151,18 @@ void GroupCall::handlePossibleCreateOrJoinResponse( } else if (_id != data.vid().v || !_instance) { return; } - const auto streamDcId = MTP::BareDcId( - data.vstream_dc_id().value_or_empty()); - const auto params = data.vparams(); - if (!params) { + if (const auto streamDcId = data.vstream_dc_id()) { + _broadcastDcId = MTP::BareDcId(streamDcId->v); + } +} + +void GroupCall::handlePossibleCreateOrJoinResponse( + const MTPDupdateGroupCallConnection &data) { + if (data.is_presentation()) { + // #TODO calls return; } - params->match([&](const MTPDdataJSON &data) { + data.vparams().match([&](const MTPDdataJSON &data) { auto error = QJsonParseError{ 0, QJsonParseError::NoError }; const auto document = QJsonDocument::fromJson( data.vdata().v, @@ -1222,60 +1183,117 @@ void GroupCall::handlePossibleCreateOrJoinResponse( }); if (document.object().value("stream").toBool()) { - if (!streamDcId) { + if (!_broadcastDcId) { LOG(("Api Error: Empty stream_dc_id in groupCall.")); + _broadcastDcId = _peer->session().mtp().mainDcId(); } - _broadcastDcId = streamDcId - ? streamDcId - : _peer->session().mtp().mainDcId(); setInstanceMode(InstanceMode::Stream); return; } const auto readString = []( - const QJsonObject &object, - const char *key) { + const QJsonObject &object, + const char *key) { return object.value(key).toString().toStdString(); }; const auto root = document.object().value("transport").toObject(); + const auto video = document.object().value("video").toObject(); auto payload = tgcalls::GroupJoinResponsePayload(); - payload.ufrag = readString(root, "ufrag"); - payload.pwd = readString(root, "pwd"); + payload.serverVideoBandwidthProbingSsrc = uint32_t( + video.value("server_sources").toArray().at(0).toDouble()); + payload.ufrag = ReadJsonString(root, "ufrag"); + payload.pwd = ReadJsonString(root, "pwd"); const auto prints = root.value("fingerprints").toArray(); const auto candidates = root.value("candidates").toArray(); for (const auto &print : prints) { const auto object = print.toObject(); payload.fingerprints.push_back({ - .hash = readString(object, "hash"), - .setup = readString(object, "setup"), - .fingerprint = readString(object, "fingerprint"), + .hash = ReadJsonString(object, "hash"), + .setup = ReadJsonString(object, "setup"), + .fingerprint = ReadJsonString(object, "fingerprint"), }); } for (const auto &candidate : candidates) { const auto object = candidate.toObject(); payload.candidates.push_back({ - .port = readString(object, "port"), - .protocol = readString(object, "protocol"), - .network = readString(object, "network"), - .generation = readString(object, "generation"), - .id = readString(object, "id"), - .component = readString(object, "component"), - .foundation = readString(object, "foundation"), - .priority = readString(object, "priority"), - .ip = readString(object, "ip"), - .type = readString(object, "type"), - .tcpType = readString(object, "tcpType"), - .relAddr = readString(object, "relAddr"), - .relPort = readString(object, "relPort"), + .port = ReadJsonString(object, "port"), + .protocol = ReadJsonString(object, "protocol"), + .network = ReadJsonString(object, "network"), + .generation = ReadJsonString(object, "generation"), + .id = ReadJsonString(object, "id"), + .component = ReadJsonString(object, "component"), + .foundation = ReadJsonString(object, "foundation"), + .priority = ReadJsonString(object, "priority"), + .ip = ReadJsonString(object, "ip"), + .type = ReadJsonString(object, "type"), + .tcpType = ReadJsonString(object, "tcpType"), + .relAddr = ReadJsonString(object, "relAddr"), + .relPort = ReadJsonString(object, "relPort"), }); } + + parseCommonVideoFields(video); + setInstanceMode(InstanceMode::Rtc); _instance->setJoinResponsePayload(payload, {}); - - addParticipantsToInstance(); }); } +void GroupCall::parseCommonVideoFields(const QJsonObject &root) { + using namespace tgcalls; + + _commonVideoFields = std::make_unique(); + const auto raw = _commonVideoFields.get(); + + const auto payloadTypes = root.value("payload-types").toArray(); + raw->videoPayloadTypes.reserve(payloadTypes.size()); + for (const auto &value : payloadTypes) { + const auto inner = value.toObject(); + + auto types = std::vector(); + { + const auto list = inner.value("rtcp-fbs").toArray(); + types.reserve(list.size()); + for (const auto &type : list) { + const auto inside = type.toObject(); + types.push_back({ + .type = ReadJsonString(inside, "type"), + .subtype = ReadJsonString(inside, "subtype"), + }); + } + } + auto parameters = std::vector>(); + { + const auto list = inner.value("parameters").toObject(); + parameters.reserve(list.size()); + for (auto i = list.begin(); i != list.end(); ++i) { + parameters.push_back({ + i.key().toStdString(), + i.value().toString().toStdString(), + }); + } + } + raw->videoPayloadTypes.push_back({ + .id = uint32_t(inner.value("id").toDouble()), + .name = ReadJsonString(inner, "name"), + .clockrate = uint32_t(inner.value("clockrate").toDouble()), + .channels = uint32_t(inner.value("channels").toDouble()), + .feedbackTypes = std::move(types), + .parameters = std::move(parameters), + }); + } + + const auto extensionMap = root.value("rtp-hdrexts").toArray(); + raw->videoExtensionMap.reserve(extensionMap.size()); + for (const auto &extension : extensionMap) { + const auto inner = extension.toObject(); + raw->videoExtensionMap.push_back({ + uint32_t(inner.value("id").toDouble()), + ReadJsonString(inner, "uri"), + }); + } +} + void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { if (data.vid().v == _id) { LOG(("Call Info: Hangup after groupCallDiscarded.")); @@ -1286,7 +1304,9 @@ void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { void GroupCall::addParticipantsToInstance() { const auto real = lookupReal(); - if (!real || (_instanceMode == InstanceMode::None)) { + if (!real + || (_instanceMode == InstanceMode::None) + || (_instanceMode == InstanceMode::Rtc && !_commonVideoFields)) { return; } for (const auto &participant : real->participants()) { @@ -1297,10 +1317,15 @@ void GroupCall::addParticipantsToInstance() { void GroupCall::prepareParticipantForAdding( const Data::GroupCallParticipant &participant) { - _preparedParticipants.push_back(participant.videoParams + const auto withVideo = _commonVideoFields && participant.videoParams; + _preparedParticipants.push_back(withVideo ? participant.videoParams->description : tgcalls::GroupParticipantDescription()); auto &added = _preparedParticipants.back(); + if (withVideo) { + added.videoSourceGroups = _commonVideoFields->videoSourceGroups; + added.videoExtensionMap = _commonVideoFields->videoExtensionMap; + } added.audioSsrc = participant.ssrc; _unresolvedSsrcs.remove(added.audioSsrc); for (const auto &group : added.videoSourceGroups) { @@ -1624,7 +1649,7 @@ void GroupCall::ensureControllerCreated() { }); return result; }, - .videoContentType = tgcalls::VideoContentType::Screencast, + .videoContentType = tgcalls::VideoContentType::Generic, }; if (Logs::DebugEnabled()) { auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); @@ -1953,20 +1978,33 @@ void GroupCall::checkJoined() { if (state() != State::Connecting || !_id || !_mySsrc) { return; } + auto sources = QVector(1, MTP_int(_mySsrc)); + if (_screencastSsrc) { + sources.push_back(MTP_int(_screencastSsrc)); + } _api.request(MTPphone_CheckGroupCall( inputCall(), - MTP_int(_mySsrc) - )).done([=](const MTPBool &result) { - if (!mtpIsTrue(result)) { - LOG(("Call Info: Rejoin after FALSE in checkGroupCall.")); + MTP_vector(std::move(sources)) + )).done([=](const MTPVector &result) { + if (!ranges::contains(result.v, MTP_int(_mySsrc))) { + LOG(("Call Info: Rejoin after no _mySsrc in checkGroupCall.")); rejoin(); } else if (state() == State::Connecting) { _checkJoinedTimer.callOnce(kCheckJoinedTimeout); } + if (_screencastSsrc + && !ranges::contains(result.v, MTP_int(_screencastSsrc))) { + LOG(("Call Info: " + "Rejoin presentation after _screencastSsrc not found.")); + // #TODO calls + } }).fail([=](const MTP::Error &error) { - LOG(("Call Info: Rejoin after error '%1' in checkGroupCall." + LOG(("Call Info: Full rejoin after error '%1' in checkGroupCall." ).arg(error.type())); rejoin(); + if (_screencastSsrc) { + // #TODO calls + } }).send(); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 4ff1f7457..39766ea35 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -143,6 +143,8 @@ public: void join(const MTPInputGroupCall &inputCall); void handleUpdate(const MTPUpdate &update); void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data); + void handlePossibleCreateOrJoinResponse( + const MTPDupdateGroupCallConnection &data); void changeTitle(const QString &title); void toggleRecording(bool enabled, const QString &title); [[nodiscard]] bool recordingStoppedByMe() const { @@ -299,6 +301,7 @@ private: void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); + void parseCommonVideoFields(const QJsonObject &root); void ensureControllerCreated(); void destroyController(); @@ -391,6 +394,8 @@ private: uint64 _id = 0; uint64 _accessHash = 0; uint32 _mySsrc = 0; + uint32 _screencastSsrc = 0; + std::unique_ptr _commonVideoFields; TimeId _scheduleDate = 0; base::flat_set _mySsrcs; mtpRequestId _createRequestId = 0; diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index d6895e20d..2b61c487c 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -549,8 +549,8 @@ void GroupCall::applyParticipantsSlice( .peer = participantPeer, .videoParams = (hasVideoParamsInformation ? Calls::ParseVideoParams( - (data.vparams() - ? data.vparams()->c_dataJSON().vdata().v + (data.vvideo() + ? data.vvideo()->c_dataJSON().vdata().v : QByteArray()), (i != end(_participants) ? i->videoParams @@ -568,7 +568,7 @@ void GroupCall::applyParticipantsSlice( .muted = data.is_muted(), .mutedByMe = mutedByMe, .canSelfUnmute = canSelfUnmute, - .videoMuted = data.is_video_muted(), + .videoMuted = (data.vvideo() == nullptr), .onlyMinLoaded = onlyMinLoaded, }; if (i == end(_participants)) { From 022c0a1327791e7e49da6e8865be32a314e81794 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 29 Apr 2021 15:36:53 +0400 Subject: [PATCH 090/477] Update tgcalls library. --- .../calls/group/calls_group_call.cpp | 293 ++---------------- .../calls/group/calls_group_call.h | 5 +- Telegram/SourceFiles/data/data_group_call.cpp | 3 + Telegram/ThirdParty/tgcalls | 2 +- Telegram/cmake/lib_tgcalls.cmake | 10 + 5 files changed, 36 insertions(+), 277 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 8aed5a0ba..cb85e416f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -87,7 +87,7 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); struct VideoParams { tgcalls::GroupParticipantDescription description; base::flat_set videoSsrcs; - uint32 hash = 0; + uint64 hash = 0; }; class GroupCall::LoadPartTask final : public tgcalls::BroadcastPartTask { @@ -152,14 +152,16 @@ struct GroupCall::LargeTrack { } std::shared_ptr ParseVideoParams( - const QByteArray &json, + const QByteArray &video, + const QByteArray &screencast, const std::shared_ptr &existing) { using namespace tgcalls; - if (json.isEmpty()) { + if (video.isEmpty() && screencast.isEmpty()) { return nullptr; } - const auto hash = XXH32(json.data(), json.size(), uint32(0)); + const auto hash = XXH32(screencast.data(), screencast.size(), uint32(0)) + | (uint64(XXH32(video.data(), video.size(), uint32(0))) << 32); if (existing && existing->hash == hash) { return existing; } @@ -169,44 +171,8 @@ std::shared_ptr ParseVideoParams( ? existing : */std::make_shared(); data->hash = hash; - - auto error = QJsonParseError{ 0, QJsonParseError::NoError }; - const auto document = QJsonDocument::fromJson(json, &error); - if (error.error != QJsonParseError::NoError) { - LOG(("API Error: " - "Failed to parse group call video params, error: %1." - ).arg(error.errorString())); - return data; - } else if (!document.isObject()) { - LOG(("API Error: " - "Not an object received in group call video params.")); - return data; - } - - const auto object = document.object(); - data->description.endpointId = ReadJsonString(object, "endpoint"); - - const auto ssrcGroups = object.value("ssrc-groups").toArray(); - data->description.videoSourceGroups.reserve(ssrcGroups.size()); - for (const auto &value : ssrcGroups) { - const auto inner = value.toObject(); - auto sources = std::vector(); - { - const auto list = inner.value("sources").toArray(); - sources.reserve(list.size()); - for (const auto &source : list) { - const auto ssrc = uint32_t(source.toDouble()); - sources.push_back(ssrc); - data->videoSsrcs.emplace(ssrc); - } - } - data->description.videoSourceGroups.push_back({ - .ssrcs = std::move(sources), - .semantics = ReadJsonString(inner, "semantics"), - }); - } - - // videoPayloadTypes and videoExtensionMap will be in _commonVideoFields. + data->description.videoInformation = video.toStdString(); + data->description.screencastInformation = screencast.toStdString(); return data; } @@ -719,97 +685,11 @@ void GroupCall::rejoin(not_null as) { const auto weak = base::make_weak(this); _instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { crl::on_main(weak, [=, payload = std::move(payload)]{ - auto fingerprints = QJsonArray(); - for (const auto &print : payload.fingerprints) { - auto object = QJsonObject(); - object.insert("hash", QString::fromStdString(print.hash)); - object.insert("setup", QString::fromStdString(print.setup)); - object.insert( - "fingerprint", - QString::fromStdString(print.fingerprint)); - fingerprints.push_back(object); - } - - auto extensionMap = QJsonArray(); - for (const auto &extension : payload.videoExtensionMap) { - auto object = QJsonObject(); - object.insert("id", int64(extension.first)); - object.insert( - "uri", - QString::fromStdString(extension.second)); - extensionMap.push_back(object); - } - - auto payloadTypes = QJsonArray(); - for (const auto &type : payload.videoPayloadTypes) { - auto object = QJsonObject(); - object.insert("id", int64(type.id)); - object.insert("name", QString::fromStdString(type.name)); - object.insert("clockrate", int64(type.clockrate)); - if (!type.parameters.empty()) { - auto parameters = QJsonObject(); - for (const auto ¶meter : type.parameters) { - parameters.insert( - QString::fromStdString(parameter.first), - QString::fromStdString(parameter.second)); - } - object.insert("parameters", parameters); - } - if (type.name != "rtx") { - object.insert("channels", int64(type.channels)); - auto fbs = QJsonArray(); - for (const auto &element : type.feedbackTypes) { - auto inner = QJsonObject(); - inner.insert( - "type", - QString::fromStdString(element.type)); - if (!element.subtype.empty()) { - inner.insert( - "subtype", - QString::fromStdString(element.subtype)); - } - fbs.push_back(inner); - } - object.insert("rtcp-fbs", fbs); - } - payloadTypes.push_back(object); - } - - auto sourceGroups = QJsonArray(); - for (const auto &group : payload.videoSourceGroups) { - auto object = QJsonObject(); - object.insert( - "semantics", - QString::fromStdString(group.semantics)); - auto list = QJsonArray(); - for (const auto source : group.ssrcs) { - list.push_back(int64(source)); - } - object.insert("sources", list); - sourceGroups.push_back(object); - } - - auto root = QJsonObject(); - const auto ssrc = payload.ssrc; - root.insert("ufrag", QString::fromStdString(payload.ufrag)); - root.insert("pwd", QString::fromStdString(payload.pwd)); - root.insert("fingerprints", fingerprints); - root.insert("ssrc", double(payload.ssrc)); - if (!extensionMap.isEmpty()) { - root.insert("rtp-hdrexts", extensionMap); - } - if (!payloadTypes.isEmpty()) { - root.insert("payload-types", payloadTypes); - } - if (!sourceGroups.isEmpty()) { - root.insert("ssrc-groups", sourceGroups); - } - + const auto ssrc = payload.audioSsrc; LOG(("Call Info: Join payload received, joining with ssrc: %1." ).arg(ssrc)); - const auto json = QJsonDocument(root).toJson( - QJsonDocument::Compact); + const auto json = QByteArray::fromStdString(payload.json); const auto wasMuteState = muted(); using Flag = MTPphone_JoinGroupCall::Flag; _api.request(MTPphone_JoinGroupCall( @@ -1163,137 +1043,11 @@ void GroupCall::handlePossibleCreateOrJoinResponse( return; } data.vparams().match([&](const MTPDdataJSON &data) { - auto error = QJsonParseError{ 0, QJsonParseError::NoError }; - const auto document = QJsonDocument::fromJson( - data.vdata().v, - &error); - if (error.error != QJsonParseError::NoError) { - LOG(("API Error: " - "Failed to parse group call params, error: %1." - ).arg(error.errorString())); - return; - } else if (!document.isObject()) { - LOG(("API Error: " - "Not an object received in group call params.")); - return; - } - - const auto guard = gsl::finally([&] { - addParticipantsToInstance(); - }); - - if (document.object().value("stream").toBool()) { - if (!_broadcastDcId) { - LOG(("Api Error: Empty stream_dc_id in groupCall.")); - _broadcastDcId = _peer->session().mtp().mainDcId(); - } - setInstanceMode(InstanceMode::Stream); - return; - } - - const auto readString = []( - const QJsonObject &object, - const char *key) { - return object.value(key).toString().toStdString(); - }; - const auto root = document.object().value("transport").toObject(); - const auto video = document.object().value("video").toObject(); - auto payload = tgcalls::GroupJoinResponsePayload(); - payload.serverVideoBandwidthProbingSsrc = uint32_t( - video.value("server_sources").toArray().at(0).toDouble()); - payload.ufrag = ReadJsonString(root, "ufrag"); - payload.pwd = ReadJsonString(root, "pwd"); - const auto prints = root.value("fingerprints").toArray(); - const auto candidates = root.value("candidates").toArray(); - for (const auto &print : prints) { - const auto object = print.toObject(); - payload.fingerprints.push_back({ - .hash = ReadJsonString(object, "hash"), - .setup = ReadJsonString(object, "setup"), - .fingerprint = ReadJsonString(object, "fingerprint"), - }); - } - for (const auto &candidate : candidates) { - const auto object = candidate.toObject(); - payload.candidates.push_back({ - .port = ReadJsonString(object, "port"), - .protocol = ReadJsonString(object, "protocol"), - .network = ReadJsonString(object, "network"), - .generation = ReadJsonString(object, "generation"), - .id = ReadJsonString(object, "id"), - .component = ReadJsonString(object, "component"), - .foundation = ReadJsonString(object, "foundation"), - .priority = ReadJsonString(object, "priority"), - .ip = ReadJsonString(object, "ip"), - .type = ReadJsonString(object, "type"), - .tcpType = ReadJsonString(object, "tcpType"), - .relAddr = ReadJsonString(object, "relAddr"), - .relPort = ReadJsonString(object, "relPort"), - }); - } - - parseCommonVideoFields(video); - setInstanceMode(InstanceMode::Rtc); - _instance->setJoinResponsePayload(payload, {}); + _instance->setJoinResponsePayload(data.vdata().v.toStdString()); }); } -void GroupCall::parseCommonVideoFields(const QJsonObject &root) { - using namespace tgcalls; - - _commonVideoFields = std::make_unique(); - const auto raw = _commonVideoFields.get(); - - const auto payloadTypes = root.value("payload-types").toArray(); - raw->videoPayloadTypes.reserve(payloadTypes.size()); - for (const auto &value : payloadTypes) { - const auto inner = value.toObject(); - - auto types = std::vector(); - { - const auto list = inner.value("rtcp-fbs").toArray(); - types.reserve(list.size()); - for (const auto &type : list) { - const auto inside = type.toObject(); - types.push_back({ - .type = ReadJsonString(inside, "type"), - .subtype = ReadJsonString(inside, "subtype"), - }); - } - } - auto parameters = std::vector>(); - { - const auto list = inner.value("parameters").toObject(); - parameters.reserve(list.size()); - for (auto i = list.begin(); i != list.end(); ++i) { - parameters.push_back({ - i.key().toStdString(), - i.value().toString().toStdString(), - }); - } - } - raw->videoPayloadTypes.push_back({ - .id = uint32_t(inner.value("id").toDouble()), - .name = ReadJsonString(inner, "name"), - .clockrate = uint32_t(inner.value("clockrate").toDouble()), - .channels = uint32_t(inner.value("channels").toDouble()), - .feedbackTypes = std::move(types), - .parameters = std::move(parameters), - }); - } - - const auto extensionMap = root.value("rtp-hdrexts").toArray(); - raw->videoExtensionMap.reserve(extensionMap.size()); - for (const auto &extension : extensionMap) { - const auto inner = extension.toObject(); - raw->videoExtensionMap.push_back({ - uint32_t(inner.value("id").toDouble()), - ReadJsonString(inner, "uri"), - }); - } -} - void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { if (data.vid().v == _id) { LOG(("Call Info: Hangup after groupCallDiscarded.")); @@ -1304,9 +1058,7 @@ void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { void GroupCall::addParticipantsToInstance() { const auto real = lookupReal(); - if (!real - || (_instanceMode == InstanceMode::None) - || (_instanceMode == InstanceMode::Rtc && !_commonVideoFields)) { + if (!real || (_instanceMode == InstanceMode::None)) { return; } for (const auto &participant : real->participants()) { @@ -1317,22 +1069,17 @@ void GroupCall::addParticipantsToInstance() { void GroupCall::prepareParticipantForAdding( const Data::GroupCallParticipant &participant) { - const auto withVideo = _commonVideoFields && participant.videoParams; - _preparedParticipants.push_back(withVideo + _preparedParticipants.push_back(participant.videoParams ? participant.videoParams->description : tgcalls::GroupParticipantDescription()); auto &added = _preparedParticipants.back(); - if (withVideo) { - added.videoSourceGroups = _commonVideoFields->videoSourceGroups; - added.videoExtensionMap = _commonVideoFields->videoExtensionMap; - } added.audioSsrc = participant.ssrc; _unresolvedSsrcs.remove(added.audioSsrc); - for (const auto &group : added.videoSourceGroups) { - for (const auto ssrc : group.ssrcs) { - _unresolvedSsrcs.remove(ssrc); - } - } + //for (const auto &group : added.videoSourceGroups) { // #TODO calls + // for (const auto ssrc : group.ssrcs) { + // _unresolvedSsrcs.remove(ssrc); + // } + //} } void GroupCall::addPreparedParticipants() { @@ -1516,7 +1263,7 @@ void GroupCall::setupOutgoingVideo() { _videoOutgoing->stateValue( ) | rpl::start_with_next([=](Webrtc::VideoState state) { //if (state != Webrtc::VideoState::Inactive && !hasDevices()) { - //_errors.fire({ ErrorType::NoCamera }); // #TODO videochats + //_errors.fire({ ErrorType::NoCamera }); // #TODO calls //_videoOutgoing->setState(Webrtc::VideoState::Inactive); //} else if (state != Webrtc::VideoState::Inactive // && _instance @@ -1534,7 +1281,7 @@ void GroupCall::setupOutgoingVideo() { _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } if (_instance) { - _instance->setVideoCapture(_videoCapture, nullptr); + _instance->setVideoCapture(_videoCapture); } _videoCapture->setState(tgcalls::VideoState::Active); } else if (_videoCapture) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 39766ea35..852c6b272 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -82,7 +82,8 @@ struct StreamsVideoUpdate { struct VideoParams; [[nodiscard]] std::shared_ptr ParseVideoParams( - const QByteArray &json, + const QByteArray &video, + const QByteArray &screencast, const std::shared_ptr &existing); [[nodiscard]] const base::flat_set &VideoSourcesFromParams( const std::shared_ptr ¶ms); @@ -301,7 +302,6 @@ private: void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); - void parseCommonVideoFields(const QJsonObject &root); void ensureControllerCreated(); void destroyController(); @@ -395,7 +395,6 @@ private: uint64 _accessHash = 0; uint32 _mySsrc = 0; uint32 _screencastSsrc = 0; - std::unique_ptr _commonVideoFields; TimeId _scheduleDate = 0; base::flat_set _mySsrcs; mtpRequestId _createRequestId = 0; diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 2b61c487c..98c56c1e4 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -552,6 +552,9 @@ void GroupCall::applyParticipantsSlice( (data.vvideo() ? data.vvideo()->c_dataJSON().vdata().v : QByteArray()), + (data.vpresentation() + ? data.vpresentation()->c_dataJSON().vdata().v + : QByteArray()), (i != end(_participants) ? i->videoParams : nullptr)) diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 909612ca6..9928c00d2 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 909612ca66cef26deffa5dc2dcd964ddde1b4c11 +Subproject commit 9928c00d231c1194896d582a71e3bb6d70ee2765 diff --git a/Telegram/cmake/lib_tgcalls.cmake b/Telegram/cmake/lib_tgcalls.cmake index 773ce44c6..c84317f67 100644 --- a/Telegram/cmake/lib_tgcalls.cmake +++ b/Telegram/cmake/lib_tgcalls.cmake @@ -67,6 +67,10 @@ PRIVATE # Group calls group/GroupInstanceCustomImpl.cpp group/GroupInstanceCustomImpl.h + group/GroupInstanceImpl.h + group/GroupJoinPayloadInternal.cpp + group/GroupJoinPayloadInternal.h + group/GroupJoinPayload.h group/GroupNetworkManager.cpp group/GroupNetworkManager.h group/StreamingPart.cpp @@ -135,6 +139,10 @@ PRIVATE # All reference/InstanceImplReference.cpp reference/InstanceImplReference.h + + # third-party + third-party/json11.cpp + third-party/json11.hpp ) target_link_libraries(lib_tgcalls @@ -145,6 +153,8 @@ PRIVATE ) target_compile_definitions(lib_tgcalls +PUBLIC + TGCALLS_USE_STD_OPTIONAL PRIVATE WEBRTC_APP_TDESKTOP RTC_ENABLE_VP9 From a48649987ecf9ceec04a369ccf73b5d26dfa5dfc Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 29 Apr 2021 20:28:12 +0400 Subject: [PATCH 091/477] Improve screencast source choosing design. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/calls/calls.style | 63 ++++- .../calls/group/calls_group_call.cpp | 7 +- .../calls/group/calls_group_call.h | 3 +- .../calls/group/calls_group_members.cpp | 6 +- .../calls/group/calls_group_panel.cpp | 8 + .../calls/group/calls_group_panel.h | 2 + .../ui/desktop_capture_choose_source.cpp | 258 +++++++++++++++--- .../group/ui/desktop_capture_choose_source.h | 2 + Telegram/SourceFiles/ui/image/image.cpp | 10 - Telegram/SourceFiles/ui/image/image.h | 3 - .../ui/image/image_location_factory.cpp | 13 + 12 files changed, 304 insertions(+), 74 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ac5d5e130..4cf23232f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2000,6 +2000,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_share_button" = "Share"; "lng_group_call_video" = "Video"; "lng_group_call_screen_share" = "Share"; +"lng_group_call_screen_share_start" = "Share Screen"; +"lng_group_call_screen_share_stop" = "Stop Sharing"; +"lng_group_call_screen_title" = "Screen {index}"; "lng_group_call_unmute_small" = "Unmute"; "lng_group_call_you_are_live_small" = "Mute"; "lng_group_call_force_muted_small" = "Muted"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 6a0222abc..e346a43ee 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -856,17 +856,18 @@ groupCallTopBarOpen: RoundButton(groupCallTopBarJoin) { color: shadowFg; } } -groupCallBox: Box(defaultBox) { - button: RoundButton(defaultBoxButton) { - textFg: groupCallActiveFg; - textFgOver: groupCallActiveFg; - numbersTextFg: groupCallActiveFg; - numbersTextFgOver: groupCallActiveFg; - textBg: groupCallMembersBg; - textBgOver: groupCallMembersBgOver; +groupCallBoxButton: RoundButton(defaultBoxButton) { + textFg: groupCallActiveFg; + textFgOver: groupCallActiveFg; + numbersTextFg: groupCallActiveFg; + numbersTextFgOver: groupCallActiveFg; + textBg: groupCallMembersBg; + textBgOver: groupCallMembersBgOver; - ripple: groupCallRipple; - } + ripple: groupCallRipple; +} +groupCallBox: Box(defaultBox) { + button: groupCallBoxButton; margin: margins(0px, 56px, 0px, 10px); bg: groupCallMembersBg; title: FlatLabel(boxTitle) { @@ -1092,8 +1093,46 @@ groupCallStartsWhenTop: 160px; groupCallCountdownFont: font(64px semibold); groupCallCountdownTop: 52px; -desktopCaptureSourceSize: size(160px, 120px); -desktopCaptureSourceSkip: 12px; +desktopCaptureMargins: margins(12px, 8px, 12px, 6px); +desktopCaptureSourceSize: size(235px, 165px); +desktopCaptureSourceSkips: size(2px, 10px); +desktopCaptureSourceTitle: WindowTitle(groupCallTitle) { + height: 21px; +} +desktopCapturePadding: margins(7px, 7px, 7px, 33px); +desktopCaptureLabelBottom: 7px; +desktopCaptureLabel: FlatLabel(defaultFlatLabel) { + minWidth: 200px; + maxHeight: 20px; + textFg: groupCallMembersFg; + style: semiboldTextStyle; +} +desktopCaptureCancel: RoundButton(defaultBoxButton) { + textFg: groupCallActiveFg; + textFgOver: groupCallActiveFg; + numbersTextFg: groupCallActiveFg; + numbersTextFgOver: groupCallActiveFg; + textBg: groupCallBg; + textBgOver: groupCallMembersBg; + + ripple: groupCallRipple; +} +desktopCaptureFinish: RoundButton(desktopCaptureCancel) { + textFg: groupCallMemberMutedIcon; + textFgOver: groupCallMemberMutedIcon; +} +desktopCaptureSubmit: RoundButton(desktopCaptureCancel) { + textFg: groupCallIconFg; + textFgOver: groupCallIconFg; + numbersTextFg: groupCallIconFg; + numbersTextFgOver: groupCallIconFg; + textBg: groupCallMuted1; + textBgOver: groupCallMuted1; + + ripple: RippleAnimation(groupCallRipple) { + color: groupCallMuted2; + } +} groupCallNarrowSkip: 9px; groupCallNarrowRowSkip: 8px; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index cb85e416f..5e97a2078 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -322,7 +322,12 @@ GroupCall::~GroupCall() { } bool GroupCall::isScreenSharing() const { - return (_videoDeviceId != _videoInputId); + return (_videoDeviceId != _videoInputId) + && (_videoOutgoing->state() == Webrtc::VideoState::Active); +} + +QString GroupCall::screenSharingDeviceId() const { + return isScreenSharing() ? _videoDeviceId : QString(); } void GroupCall::toggleVideo(bool active) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 852c6b272..1aa4ea44e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -248,7 +248,8 @@ public: void setCurrentAudioDevice(bool input, const QString &deviceId); void setCurrentVideoDevice(const QString &deviceId); - bool isScreenSharing() const; + [[nodiscard]] bool isScreenSharing() const; + [[nodiscard]] QString screenSharingDeviceId() const; void toggleVideo(bool active); void switchToScreenSharing(const QString &uniqueId); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index ce7908782..e61385081 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1460,9 +1460,9 @@ void MembersController::updateRow( Assert(nowSsrc != 0); _soundingRowBySsrc.emplace(nowSsrc, row); } - if (isMe(row->peer())) { - row->setVideoTrack(_call->outgoingVideoTrack()); - } + //if (isMe(row->peer())) { + // row->setVideoTrack(_call->outgoingVideoTrack()); + //} } const auto nowNoSounding = _soundingRowBySsrc.empty(); if (wasNoSounding && !nowNoSounding) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 75602a10f..b0116ae32 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -484,6 +484,10 @@ QWidget *Panel::chooseSourceParent() { return _window.get(); } +QString Panel::chooseSourceActiveDeviceId() { + return _call->screenSharingDeviceId(); +} + rpl::lifetime &Panel::chooseSourceInstanceLifetime() { return _window->lifetime(); } @@ -492,6 +496,10 @@ void Panel::chooseSourceAccepted(const QString &deviceId) { _call->switchToScreenSharing(deviceId); } +void Panel::chooseSourceStop() { + _call->toggleVideo(false); +} + void Panel::initWindow() { _window->setAttribute(Qt::WA_OpaquePaintEvent); _window->setAttribute(Qt::WA_NoSystemBackground); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index ed14ca4c3..54522397d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -116,8 +116,10 @@ private: void subscribeToPeerChanges(); QWidget *chooseSourceParent() override; + QString chooseSourceActiveDeviceId() override; rpl::lifetime &chooseSourceInstanceLifetime() override; void chooseSourceAccepted(const QString &deviceId) override; + void chooseSourceStop() override; const not_null _call; not_null _peer; diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp index 73d4a2319..0aed00c13 100644 --- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp +++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp @@ -11,8 +11,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" +#include "ui/effects/ripple_animation.h" +#include "ui/image/image.h" +#include "ui/platform/ui_platform_window_title.h" #include "base/platform/base_platform_info.h" #include "webrtc/webrtc_video_track.h" +#include "lang/lang_keys.h" #include "styles/style_calls.h" #include @@ -33,6 +37,19 @@ struct Preview { rpl::lifetime lifetime; }; +class SourceButton final : public RippleButton { +public: + using RippleButton::RippleButton; + +private: + QImage prepareRippleMask() const override; + +}; + +QImage SourceButton::prepareRippleMask() const { + return RippleAnimation::roundRectMask(size(), st::roundRadiusLarge); +} + class Source final { public: Source( @@ -43,19 +60,23 @@ public: void setGeometry(QRect geometry); void clearHelper(); - [[nodiscard]] bool ready() const; - [[nodiscard]] rpl::producer<> clicks() const; + [[nodiscard]] rpl::producer<> activations() const; + void setActive(bool active); [[nodiscard]] rpl::lifetime &lifetime(); private: void paint(); void setupPreview(); - AbstractButton _widget; + SourceButton _widget; FlatLabel _label; + RoundRect _selectedRect; + RoundRect _activeRect; tgcalls::DesktopCaptureSource _source; std::unique_ptr _preview; + rpl::event_stream<> _activations; QImage _frame; + bool _active = false; }; @@ -80,11 +101,16 @@ private: std::unique_ptr> &Map(); const not_null _delegate; - const std::unique_ptr<::Ui::Window> _window; + const std::unique_ptr _window; const std::unique_ptr _scroll; const not_null _inner; + const not_null _bottom; + const not_null _submit; + const not_null _finish; std::vector> _sources; + Source *_selected = nullptr; + QString _selectedId; }; @@ -109,25 +135,45 @@ Source::Source( not_null parent, tgcalls::DesktopCaptureSource source, const QString &title) -: _widget(parent) -, _label(&_widget, title) +: _widget(parent, st::groupCallRipple) +, _label(&_widget, title, st::desktopCaptureLabel) +, _selectedRect(ImageRoundRadius::Large, st::groupCallMembersBgOver) +, _activeRect(ImageRoundRadius::Large, st::groupCallMuted1) , _source(source) { _widget.paintRequest( ) | rpl::start_with_next([=] { paint(); }, _widget.lifetime()); + _label.setAttribute(Qt::WA_TransparentForMouseEvents); + _widget.sizeValue( ) | rpl::start_with_next([=](QSize size) { - _label.resizeToNaturalWidth(size.width()); + const auto padding = st::desktopCapturePadding; + _label.resizeToNaturalWidth( + size.width() - padding.left() - padding.right()); _label.move( (size.width() - _label.width()) / 2, - size.height() - _label.height()); + size.height() - _label.height() - st::desktopCaptureLabelBottom); }, _label.lifetime()); + + _widget.setClickedCallback([=] { + setActive(true); + }); } -rpl::producer<> Source::clicks() const { - return _widget.clicks() | rpl::to_empty; +rpl::producer<> Source::activations() const { + return _activations.events(); +} + +void Source::setActive(bool active) { + if (_active != active) { + _active = active; + _widget.update(); + if (active) { + _activations.fire({}); + } + } } void Source::setGeometry(QRect geometry) { @@ -144,14 +190,21 @@ void Source::paint() { if (_frame.isNull() && !_preview) { setupPreview(); } + if (_active) { + _activeRect.paint(p, _widget.rect()); + } else if (_widget.isOver() || _widget.isDown()) { + _selectedRect.paint(p, _widget.rect()); + } + _widget.paintRipple( + p, + { 0, 0 }, + _active ? &st::groupCallMuted2->c : nullptr); + const auto size = _preview ? _preview->track.frameSize() : QSize(); const auto factor = style::DevicePixelRatio(); + const auto padding = st::desktopCapturePadding; const auto rect = _widget.rect(); - const auto inner = QRect( - rect.x(), - rect.y(), - rect.width(), - rect.height() - _label.height()); + const auto inner = rect.marginsRemoved(padding); if (!size.isEmpty()) { const auto scaled = size.scaled(inner.size(), Qt::KeepAspectRatio); const auto request = Webrtc::FrameRequest{ @@ -190,9 +243,20 @@ rpl::lifetime &Source::lifetime() { ChooseSourceProcess::ChooseSourceProcess( not_null delegate) : _delegate(delegate) -, _window(std::make_unique<::Ui::Window>()) +, _window(std::make_unique()) , _scroll(std::make_unique(_window->body())) -, _inner(_scroll->setOwnedWidget(object_ptr(_scroll.get()))) { +, _inner(_scroll->setOwnedWidget(object_ptr(_scroll.get()))) +, _bottom(CreateChild(_window->body().get())) +, _submit( + CreateChild( + _bottom.get(), + tr::lng_group_call_screen_share_start(), + st::desktopCaptureSubmit)) +, _finish( + CreateChild( + _bottom.get(), + tr::lng_group_call_screen_share_stop(), + st::desktopCaptureFinish)) { setupPanel(); setupSources(); activate(); @@ -233,25 +297,102 @@ void ChooseSourceProcess::activate() { } void ChooseSourceProcess::setupPanel() { - const auto width = kColumns * st::desktopCaptureSourceSize.width() - + (kColumns + 1) * st::desktopCaptureSourceSkip; - const auto height = kRows * st::desktopCaptureSourceSize.height() - + (kRows + 1) * st::desktopCaptureSourceSkip - + (st::desktopCaptureSourceSize.height() / 2); + _window->setAttribute(Qt::WA_OpaquePaintEvent); + _window->setAttribute(Qt::WA_NoSystemBackground); + _window->setWindowIcon(QIcon( + QPixmap::fromImage(Image::Empty()->original(), Qt::ColorOnly))); + _window->setTitleStyle(st::desktopCaptureSourceTitle); + + const auto skips = st::desktopCaptureSourceSkips; + const auto margins = st::desktopCaptureMargins; + const auto padding = st::desktopCapturePadding; + const auto bottomSkip = margins.right() + padding.right(); + const auto bottomHeight = 2 * bottomSkip + + st::desktopCaptureCancel.height; + const auto width = margins.left() + + kColumns * st::desktopCaptureSourceSize.width() + + (kColumns - 1) * skips.width() + + margins.right(); + const auto height = margins.top() + + kRows * st::desktopCaptureSourceSize.height() + + (kRows - 1) * skips.height() + + (st::desktopCaptureSourceSize.height() / 2) + + bottomHeight; _window->setFixedSize({ width, height }); _window->setWindowFlags(Qt::WindowStaysOnTopHint); + _window->body()->paintRequest( + ) | rpl::start_with_next([=](QRect clip) { + QPainter(_window->body()).fillRect(clip, st::groupCallBg); + }, _window->lifetime()); + + _bottom->setGeometry(0, height - bottomHeight, width, bottomHeight); + + _submit->setClickedCallback([=] { + if (_selectedId.isEmpty()) { + return; + } + const auto weak = MakeWeak(_window.get()); + _delegate->chooseSourceAccepted(_selectedId); + if (const auto strong = weak.data()) { + strong->close(); + } + }); + _finish->setClickedCallback([=] { + const auto weak = MakeWeak(_window.get()); + _delegate->chooseSourceStop(); + if (const auto strong = weak.data()) { + strong->close(); + } + }); + const auto cancel = CreateChild( + _bottom.get(), + tr::lng_cancel(), + st::desktopCaptureCancel); + cancel->setClickedCallback([=] { + _window->close(); + }); + + rpl::combine( + _submit->widthValue(), + _submit->shownValue(), + _finish->widthValue(), + _finish->shownValue(), + cancel->widthValue() + ) | rpl::start_with_next([=]( + int submitWidth, + bool submitShown, + int finishWidth, + bool finishShown, + int cancelWidth) { + _finish->moveToRight(bottomSkip, bottomSkip); + _submit->moveToRight(bottomSkip, bottomSkip); + cancel->moveToRight( + bottomSkip * 2 + (submitShown ? submitWidth : finishWidth), + bottomSkip); + }, _bottom->lifetime()); + + const auto sharing = !_delegate->chooseSourceActiveDeviceId().isEmpty(); + _finish->setVisible(sharing); + _submit->setVisible(!sharing); + _window->body()->sizeValue( ) | rpl::start_with_next([=](QSize size) { - _scroll->setGeometry({ QPoint(), size }); + _scroll->setGeometry( + 0, + 0, + size.width(), + size.height() - _bottom->height()); }, _scroll->lifetime()); _scroll->widthValue( ) | rpl::start_with_next([=](int width) { const auto rows = int(std::ceil(_sources.size() / float(kColumns))); - const auto height = rows * st::desktopCaptureSourceSize.height() - + (rows + 1) * st::desktopCaptureSourceSkip; - _inner->resize(width, height); + const auto innerHeight = margins.top() + + rows * st::desktopCaptureSourceSize.height() + + (rows - 1) * skips.height() + + margins.bottom(); + _inner->resize(width, std::max(height, innerHeight)); }, _inner->lifetime()); if (const auto parent = _delegate->chooseSourceParent()) { @@ -278,17 +419,42 @@ void ChooseSourceProcess::fillSources() { auto screenIndex = 0; auto windowIndex = 0; + const auto active = _delegate->chooseSourceActiveDeviceId(); const auto append = [&](const tgcalls::DesktopCaptureSource &source) { - const auto title = !source.title().empty() + const auto title = !source.isWindow() + ? tr::lng_group_call_screen_title( + tr::now, + lt_index, + QString::number(++screenIndex)) + : !source.title().empty() ? QString::fromStdString(source.title()) - : source.isWindow() - ? "Window " + QString::number(++windowIndex) - : "Screen " + QString::number(++screenIndex); + : "Window " + QString::number(++windowIndex); + const auto id = source.deviceIdKey(); _sources.push_back(std::make_unique(_inner, source, title)); - _sources.back()->clicks( - ) | rpl::start_with_next([=, id = source.deviceIdKey()]{ - _delegate->chooseSourceAccepted(QString::fromStdString(id)); - }, _sources.back()->lifetime()); + + const auto raw = _sources.back().get(); + if (!active.isEmpty() && active.toStdString() == id) { + _selected = raw; + raw->setActive(true); + } + _sources.back()->activations( + ) | rpl::filter([=] { + return (_selected != raw); + }) | rpl::start_with_next([=]{ + if (_selected) { + _selected->setActive(false); + } + _selected = raw; + _selectedId = QString::fromStdString(id); + if (_selectedId == _delegate->chooseSourceActiveDeviceId()) { + _selectedId = QString(); + _finish->setVisible(true); + _submit->setVisible(false); + } else { + _finish->setVisible(false); + _submit->setVisible(true); + } + }, raw->lifetime()); }; for (const auto &source : screensManager.sources()) { append(source); @@ -300,31 +466,34 @@ void ChooseSourceProcess::fillSources() { void ChooseSourceProcess::setupSourcesGeometry() { if (_sources.empty()) { - //LOG(()); destroy(); return; } _inner->widthValue( ) | rpl::start_with_next([=](int width) { const auto rows = int(std::ceil(_sources.size() / float(kColumns))); - const auto skip = st::desktopCaptureSourceSkip; - const auto single = (width - (kColumns + 1) * skip) / kColumns; + const auto margins = st::desktopCaptureMargins; + const auto skips = st::desktopCaptureSourceSkips; + const auto single = (width + - margins.left() + - margins.right() + - (kColumns - 1) * skips.width()) / kColumns; const auto height = st::desktopCaptureSourceSize.height(); - auto top = skip; + auto top = margins.top(); auto index = 0; for (auto row = 0; row != rows; ++row) { - auto left = skip; + auto left = margins.left(); for (auto column = 0; column != kColumns; ++column) { _sources[index]->setGeometry({ left, top, single, height }); if (++index == _sources.size()) { break; } - left += single + skip; + left += single + skips.width(); } if (index >= _sources.size()) { break; } - top += height + skip; + top += height + skips.height(); } }, _inner->lifetime()); @@ -333,9 +502,10 @@ void ChooseSourceProcess::setupSourcesGeometry() { _scroll->heightValue() ) | rpl::start_with_next([=](int scrollTop, int scrollHeight) { const auto rows = int(std::ceil(_sources.size() / float(kColumns))); - const auto skip = st::desktopCaptureSourceSkip; + const auto margins = st::desktopCaptureMargins; + const auto skips = st::desktopCaptureSourceSkips; const auto height = st::desktopCaptureSourceSize.height(); - auto top = skip; + auto top = margins.top(); auto index = 0; for (auto row = 0; row != rows; ++row) { const auto hidden = (top + height <= scrollTop) @@ -353,7 +523,7 @@ void ChooseSourceProcess::setupSourcesGeometry() { if (index >= _sources.size()) { break; } - top += height + skip; + top += height + skips.height(); } }, _inner->lifetime()); } diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.h b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.h index 22b871fa8..82f0fb9e5 100644 --- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.h +++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.h @@ -19,8 +19,10 @@ namespace Calls::Group::Ui::DesktopCapture { class ChooseSourceDelegate { public: virtual QWidget *chooseSourceParent() = 0; + virtual QString chooseSourceActiveDeviceId() = 0; virtual rpl::lifetime &chooseSourceInstanceLifetime() = 0; virtual void chooseSourceAccepted(const QString &deviceId) = 0; + virtual void chooseSourceStop() = 0; }; void ChooseSource(not_null delegate); diff --git a/Telegram/SourceFiles/ui/image/image.cpp b/Telegram/SourceFiles/ui/image/image.cpp index 0dad5e9ed..dc98c2511 100644 --- a/Telegram/SourceFiles/ui/image/image.cpp +++ b/Telegram/SourceFiles/ui/image/image.cpp @@ -93,16 +93,6 @@ QImage FromInlineBytes(const QByteArray &bytes) { return App::readImage(ExpandInlineBytes(bytes)); } -QSize GetSizeForDocument(const QVector &attributes) { - for (const auto &attribute : attributes) { - if (attribute.type() == mtpc_documentAttributeImageSize) { - auto &size = attribute.c_documentAttributeImageSize(); - return QSize(size.vw().v, size.vh().v); - } - } - return QSize(); -} - } // namespace Images Image::Image(const QString &path) : Image(ReadContent(path)) { diff --git a/Telegram/SourceFiles/ui/image/image.h b/Telegram/SourceFiles/ui/image/image.h index 07f5a6a4d..f18f7bdbb 100644 --- a/Telegram/SourceFiles/ui/image/image.h +++ b/Telegram/SourceFiles/ui/image/image.h @@ -14,9 +14,6 @@ namespace Images { [[nodiscard]] QByteArray ExpandInlineBytes(const QByteArray &bytes); [[nodiscard]] QImage FromInlineBytes(const QByteArray &bytes); -[[nodiscard]] QSize GetSizeForDocument( - const QVector &attributes); - } // namespace Images class Image final { diff --git a/Telegram/SourceFiles/ui/image/image_location_factory.cpp b/Telegram/SourceFiles/ui/image/image_location_factory.cpp index 57db9035c..dc3776d57 100644 --- a/Telegram/SourceFiles/ui/image/image_location_factory.cpp +++ b/Telegram/SourceFiles/ui/image/image_location_factory.cpp @@ -13,6 +13,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Images { +namespace { + +QSize GetSizeForDocument(const QVector &attributes) { + for (const auto &attribute : attributes) { + if (attribute.type() == mtpc_documentAttributeImageSize) { + auto &size = attribute.c_documentAttributeImageSize(); + return QSize(size.vw().v, size.vh().v); + } + } + return QSize(); +} + +} // namespace ImageWithLocation FromPhotoSize( not_null session, From ba1dade4b08369164e0da5b3d8ef81445c4f3776 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 30 Apr 2021 19:20:30 +0400 Subject: [PATCH 092/477] New API/tgcalls with two outgoing videos. --- .../calls/group/calls_group_call.cpp | 1001 ++++++++++++----- .../calls/group/calls_group_call.h | 131 ++- .../calls/group/calls_group_members.cpp | 124 +- .../calls/group/calls_group_panel.cpp | 10 +- Telegram/SourceFiles/data/data_group_call.cpp | 102 +- Telegram/SourceFiles/data/data_group_call.h | 29 +- Telegram/ThirdParty/tgcalls | 2 +- 7 files changed, 974 insertions(+), 425 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 5e97a2078..cdc22d9e4 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -80,16 +80,36 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); const QJsonObject &object, const char *key) { return object.value(key).toString().toStdString(); -}; +} + +[[nodiscard]] uint64 FindLocalRaisedHandRating( + const std::vector &list) { + const auto i = ranges::max_element( + list, + ranges::less(), + &Data::GroupCallParticipant::raisedHandRating); + return (i == end(list)) ? 1 : (i->raisedHandRating + 1); +} + +[[nodiscard]] std::string ParseVideoEndpoint(const QByteArray &json) { + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson(json, &error); + if (error.error != QJsonParseError::NoError) { + LOG(("API Error: " + "Failed to parse presentation video params, error: %1." + ).arg(error.errorString())); + return {}; + } else if (!document.isObject()) { + LOG(("API Error: " + "Not an object received in presentation video params.")); + return {}; + } + const auto video = document.object().value("video").toObject(); + return video.value("endpoint").toString().toStdString(); +} } // namespace -struct VideoParams { - tgcalls::GroupParticipantDescription description; - base::flat_set videoSsrcs; - uint64 hash = 0; -}; - class GroupCall::LoadPartTask final : public tgcalls::BroadcastPartTask { public: LoadPartTask( @@ -117,6 +137,34 @@ private: }; +class GroupCall::MediaChannelDescriptionsTask final + : public tgcalls::RequestMediaChannelDescriptionTask { +public: + MediaChannelDescriptionsTask( + base::weak_ptr call, + const std::vector &ssrcs, + Fn&&)> done); + + [[nodiscard]] base::flat_set ssrcs() const; + + [[nodiscard]] bool finishWithAdding( + uint32 ssrc, + std::optional description, + bool screen = false); + + void cancel() override; + +private: + const base::weak_ptr _call; + base::flat_set _ssrcs; + base::flat_set _cameraAdded; + base::flat_set _screenAdded; + std::vector _result; + Fn&&)> _done; + QMutex _mutex; + +}; + struct GroupCall::LargeTrack { LargeTrack() : track(Webrtc::VideoState::Active) { } @@ -151,39 +199,72 @@ struct GroupCall::LargeTrack { return false; } -std::shared_ptr ParseVideoParams( - const QByteArray &video, - const QByteArray &screencast, - const std::shared_ptr &existing) { +[[nodiscard]] VideoParams ParseVideoParams(const QByteArray &json) { + if (json.isEmpty()) { + return {}; + } + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson(json, &error); + if (error.error != QJsonParseError::NoError) { + LOG(("API Error: " + "Failed to parse group call video params, error: %1." + ).arg(error.errorString())); + return {}; + } else if (!document.isObject()) { + LOG(("API Error: " + "Not an object received in group call video params.")); + return {}; + } + + const auto object = document.object(); + auto result = VideoParams{ + .endpoint = ReadJsonString(object, "endpoint"), + .json = json, + }; + const auto ssrcGroups = object.value("ssrc-groups").toArray(); + for (const auto &value : ssrcGroups) { + const auto inner = value.toObject(); + const auto list = inner.value("sources").toArray(); + for (const auto &source : list) { + const auto ssrc = uint32_t(source.toDouble()); + result.ssrcs.emplace(ssrc); + } + } + return result.empty() ? VideoParams() : result; +} + +std::shared_ptr ParseVideoParams( + const QByteArray &camera, + const QByteArray &screen, + const std::shared_ptr &existing) { using namespace tgcalls; - if (video.isEmpty() && screencast.isEmpty()) { + if (camera.isEmpty() && screen.isEmpty()) { return nullptr; } - const auto hash = XXH32(screencast.data(), screencast.size(), uint32(0)) - | (uint64(XXH32(video.data(), video.size(), uint32(0))) << 32); - if (existing && existing->hash == hash) { + const auto cameraHash = camera.isEmpty() + ? 0 + : XXH32(camera.data(), camera.size(), uint32(0)); + const auto screenHash = screen.isEmpty() + ? 0 + : XXH32(screen.data(), screen.size(), uint32(0)); + if (existing + && existing->camera.hash == cameraHash + && existing->screen.hash == screenHash) { return existing; } // We don't reuse existing pointer, that way we can compare pointers // to see if anything was changed in video params. const auto data = /*existing ? existing - : */std::make_shared(); - data->hash = hash; - data->description.videoInformation = video.toStdString(); - data->description.screencastInformation = screencast.toStdString(); + : */std::make_shared(); + data->camera = ParseVideoParams(camera); + data->camera.hash = cameraHash; + data->screen = ParseVideoParams(screen); + data->screen.hash = screenHash; return data; } -const base::flat_set &VideoSourcesFromParams( - const std::shared_ptr ¶ms) { - static const auto kEmpty = base::flat_set(); - return (params && !params->videoSsrcs.empty()) - ? params->videoSsrcs - : kEmpty; -} - GroupCall::LoadPartTask::LoadPartTask( base::weak_ptr call, int64 time, @@ -228,6 +309,63 @@ void GroupCall::LoadPartTask::cancel() { } } +GroupCall::MediaChannelDescriptionsTask::MediaChannelDescriptionsTask( + base::weak_ptr call, + const std::vector &ssrcs, + Fn&&)> done) +: _call(std::move(call)) +, _ssrcs(ssrcs.begin(), ssrcs.end()) +, _done(std::move(done)) { +} + +auto GroupCall::MediaChannelDescriptionsTask::ssrcs() const +-> base::flat_set { + return _ssrcs; +} + +bool GroupCall::MediaChannelDescriptionsTask::finishWithAdding( + uint32 ssrc, + std::optional description, + bool screen) { + Expects(_ssrcs.contains(ssrc)); + + using Type = tgcalls::MediaChannelDescription::Type; + _ssrcs.remove(ssrc); + if (!description) { + } else if (description->type == Type::Audio + || (!screen && _cameraAdded.emplace(description->audioSsrc).second) + || (screen && _screenAdded.emplace(description->audioSsrc).second)) { + _result.push_back(std::move(*description)); + } + + if (!_ssrcs.empty()) { + return false; + } + QMutexLocker lock(&_mutex); + if (_done) { + base::take(_done)(std::move(_result)); + } + return true; +} + +void GroupCall::MediaChannelDescriptionsTask::cancel() { + QMutexLocker lock(&_mutex); + if (!_done) { + return; + } + _done = nullptr; + lock.unlock(); + + if (_call) { + const auto that = this; + crl::on_main(_call, [weak = _call, that] { + if (const auto strong = weak.get()) { + strong->mediaChannelDescriptionsCancel(that); + } + }); + } +} + GroupCall::GroupCall( not_null delegate, Group::JoinInfo info, @@ -241,7 +379,9 @@ GroupCall::GroupCall( , _joinHash(info.joinHash) , _id(inputCall.c_inputGroupCall().vid().v) , _scheduleDate(info.scheduleDate) -, _videoOutgoing( +, _cameraOutgoing( + std::make_unique(Webrtc::VideoState::Inactive)) +, _screenOutgoing( std::make_unique(Webrtc::VideoState::Inactive)) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) @@ -311,57 +451,38 @@ GroupCall::GroupCall( GroupCall::~GroupCall() { destroyController(); - const auto wasScreenSharing = isScreenSharing(); - const auto weak = wasScreenSharing - ? std::weak_ptr(_videoCapture) - : std::weak_ptr(); - _videoCapture = nullptr; - if (const auto strong = weak.lock()) { - strong->switchToDevice(_videoDeviceId.toStdString()); - } } bool GroupCall::isScreenSharing() const { - return (_videoDeviceId != _videoInputId) - && (_videoOutgoing->state() == Webrtc::VideoState::Active); + return (_screenOutgoing->state() == Webrtc::VideoState::Active); } QString GroupCall::screenSharingDeviceId() const { - return isScreenSharing() ? _videoDeviceId : QString(); + return isScreenSharing() ? _screenDeviceId : QString(); } void GroupCall::toggleVideo(bool active) { - if (!active) { - if (_videoOutgoing->state() != Webrtc::VideoState::Inactive) { - _videoOutgoing->setState(Webrtc::VideoState::Inactive); - sendSelfUpdate(SendUpdateType::VideoMuted); - } - return; - } - const auto changing = isScreenSharing(); - _videoDeviceId = _videoInputId; - if (_videoOutgoing->state() != Webrtc::VideoState::Active) { - _videoOutgoing->setState(Webrtc::VideoState::Active); - sendSelfUpdate(SendUpdateType::VideoMuted); - } - if (!_videoCapture) { - return; - } - if (changing) { - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + const auto state = active + ? Webrtc::VideoState::Active + : Webrtc::VideoState::Inactive; + if (_cameraOutgoing->state() != state) { + _cameraOutgoing->setState(state); } } -void GroupCall::switchToScreenSharing(const QString &uniqueId) { - if (_videoDeviceId == uniqueId) { +void GroupCall::toggleScreenSharing(std::optional uniqueId) { + if (!uniqueId) { + _screenOutgoing->setState(Webrtc::VideoState::Inactive); return; } - _videoDeviceId = uniqueId; - if (_videoOutgoing->state() != Webrtc::VideoState::Active) { - _videoOutgoing->setState(Webrtc::VideoState::Active); - sendSelfUpdate(SendUpdateType::VideoMuted); + const auto changed = (_screenDeviceId != *uniqueId); + _screenDeviceId = *uniqueId; + if (_screenOutgoing->state() != Webrtc::VideoState::Active) { + _screenOutgoing->setState(Webrtc::VideoState::Active); + } + if (changed) { + _screenCapture->switchToDevice(uniqueId->toStdString()); } - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } void GroupCall::setScheduledDate(TimeId date) { @@ -378,43 +499,68 @@ void GroupCall::subscribeToReal(not_null real) { setScheduledDate(date); }, _lifetime); + const auto emptyEndpoint = std::string(); + using Update = Data::GroupCall::ParticipantUpdate; real->participantUpdated( ) | rpl::start_with_next([=](const Update &data) { - auto newLarge = _videoStreamLarge.current(); - auto updateAsNotStreams = uint32(0); + auto newLarge = _videoEndpointLarge.current(); + auto updateCameraNotStreams = std::string(); + auto updateScreenNotStreams = std::string(); const auto guard = gsl::finally([&] { - if (!newLarge) { - newLarge = chooseLargeVideoSsrc(); + if (newLarge.empty()) { + newLarge = chooseLargeVideoEndpoint(); } - if (_videoStreamLarge.current() != newLarge) { - _videoStreamLarge = newLarge; + if (_videoEndpointLarge.current() != newLarge) { + _videoEndpointLarge = newLarge; } - if (updateAsNotStreams) { - _streamsVideoUpdated.fire({ updateAsNotStreams, false }); + if (!updateCameraNotStreams.empty()) { + _streamsVideoUpdated.fire({ updateCameraNotStreams, false }); + } + if (!updateScreenNotStreams.empty()) { + _streamsVideoUpdated.fire({ updateScreenNotStreams, false }); } }); - const auto wasVideoMutedSsrc = (data.was && data.was->videoMuted) - ? data.was->ssrc - : 0; - const auto nowVideoMutedSsrc = (data.now && data.now->videoMuted) - ? data.now->ssrc - : 0; - if (wasVideoMutedSsrc != nowVideoMutedSsrc) { - if (wasVideoMutedSsrc - && _videoMuted.remove(wasVideoMutedSsrc) - && _videoStreamSsrcs.contains(wasVideoMutedSsrc) - && data.now - && data.now->ssrc == wasVideoMutedSsrc) { - _streamsVideoUpdated.fire({ wasVideoMutedSsrc, true }); + const auto &wasCameraEndpoint = (data.was && data.was->videoParams) + ? data.was->videoParams->camera.endpoint + : emptyEndpoint; + const auto &nowCameraEndpoint = (data.now && data.now->videoParams) + ? data.now->videoParams->camera.endpoint + : emptyEndpoint; + if (wasCameraEndpoint != nowCameraEndpoint) { + if (!nowCameraEndpoint.empty() + && _activeVideoEndpoints.emplace(nowCameraEndpoint).second + && _incomingVideoEndpoints.contains(nowCameraEndpoint)) { + _streamsVideoUpdated.fire({ nowCameraEndpoint, true }); } - if (nowVideoMutedSsrc - && _videoMuted.emplace(nowVideoMutedSsrc).second - && _videoStreamSsrcs.contains(nowVideoMutedSsrc)) { - updateAsNotStreams = nowVideoMutedSsrc; - if (newLarge == nowVideoMutedSsrc) { - newLarge = 0; + if (!wasCameraEndpoint.empty() + && _activeVideoEndpoints.remove(wasCameraEndpoint) + && _incomingVideoEndpoints.contains(wasCameraEndpoint)) { + updateCameraNotStreams = wasCameraEndpoint; + if (newLarge == wasCameraEndpoint) { + newLarge = std::string(); + } + } + } + const auto &wasScreenEndpoint = (data.was && data.was->videoParams) + ? data.was->videoParams->screen.endpoint + : emptyEndpoint; + const auto &nowScreenEndpoint = (data.now && data.now->videoParams) + ? data.now->videoParams->screen.endpoint + : emptyEndpoint; + if (wasScreenEndpoint != nowScreenEndpoint) { + if (!nowScreenEndpoint.empty() + && _activeVideoEndpoints.emplace(nowScreenEndpoint).second + && _incomingVideoEndpoints.contains(nowScreenEndpoint)) { + _streamsVideoUpdated.fire({ nowScreenEndpoint, true }); + } + if (!wasScreenEndpoint.empty() + && _activeVideoEndpoints.remove(wasScreenEndpoint) + && _incomingVideoEndpoints.contains(wasScreenEndpoint)) { + updateScreenNotStreams = wasScreenEndpoint; + if (newLarge == wasScreenEndpoint) { + newLarge = std::string(); } } } @@ -424,42 +570,74 @@ void GroupCall::subscribeToReal(not_null real) { const auto wasSounding = data.was && data.was->sounding; if (nowSpeaking == wasSpeaking && nowSounding == wasSounding) { return; - } else if (_videoStreamPinned) { + } else if (!_videoEndpointPinned.empty()) { return; } + if (nowScreenEndpoint != newLarge + && streamsVideo(nowScreenEndpoint)) { + newLarge = nowScreenEndpoint; + } const auto &participants = real->participants(); - if ((wasSpeaking || wasSounding) - && (data.was->ssrc == newLarge)) { - auto bestWithVideoSsrc = uint32(0); + if (!nowSpeaking + && (wasSpeaking || wasSounding) + && (wasCameraEndpoint == newLarge)) { + auto screenEndpoint = std::string(); + auto speakingEndpoint = std::string(); + auto soundingEndpoint = std::string(); for (const auto &participant : participants) { - if (!participant.sounding - || !streamsVideo(participant.ssrc)) { + const auto params = participant.videoParams.get(); + if (!params) { continue; } - if (participant.speaking) { - bestWithVideoSsrc = participant.ssrc; + if (streamsVideo(params->screen.endpoint)) { + screenEndpoint = params->screen.endpoint; break; - } else if (!bestWithVideoSsrc) { - bestWithVideoSsrc = participant.ssrc; + } else if (participant.speaking + && speakingEndpoint.empty()) { + if (streamsVideo(params->camera.endpoint)) { + speakingEndpoint = params->camera.endpoint; + } + } else if (!nowSounding + && participant.sounding + && soundingEndpoint.empty()) { + if (streamsVideo(params->camera.endpoint)) { + soundingEndpoint = params->camera.endpoint; + } } } - if (bestWithVideoSsrc) { - newLarge = bestWithVideoSsrc; + if (!screenEndpoint.empty()) { + newLarge = screenEndpoint; + } else if (!speakingEndpoint.empty()) { + newLarge = speakingEndpoint; + } else if (!soundingEndpoint.empty()) { + newLarge = soundingEndpoint; } } else if ((nowSpeaking || nowSounding) - && (data.now->ssrc != newLarge) - && streamsVideo(data.now->ssrc)) { - const auto i = ranges::find( - participants, - newLarge, - &Data::GroupCallParticipant::ssrc); - const auto speaking = (i != end(participants)) && i->speaking; - const auto sounding = (i != end(participants)) && i->sounding; - if ((nowSpeaking && !speaking) || (nowSounding && !sounding)) { - newLarge = data.now->ssrc; + && (nowScreenEndpoint != newLarge) + && (nowCameraEndpoint != newLarge) + && streamsVideo(nowCameraEndpoint)) { + const auto participant = real->participantByEndpoint(newLarge); + const auto screen = participant + && (participant->videoParams->screen.endpoint == newLarge); + const auto speaking = participant && participant->speaking; + const auto sounding = participant && participant->sounding; + if (!screen + && ((nowSpeaking && !speaking) + || (nowSounding && !sounding))) { + newLarge = nowCameraEndpoint; } } }, _lifetime); + + real->participantsResolved( + ) | rpl::start_with_next([=]( + not_null*> ssrcs) { + checkMediaChannelDescriptions([&](uint32 ssrc) { + return ssrcs->contains(ssrc); + }); + }, _lifetime); } void GroupCall::checkGlobalShortcutAvailability() { @@ -595,7 +773,9 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { rejoin(); using Update = Data::GroupCall::ParticipantUpdate; - _peer->groupCall()->participantUpdated( + const auto real = lookupReal(); + Assert(real != nullptr); + real->participantUpdated( ) | rpl::filter([=](const Update &update) { return (_instance != nullptr); }) | rpl::start_with_next([=](const Update &update) { @@ -608,17 +788,6 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { ? (was->volume != now.volume || was->mutedByMe != now.mutedByMe) : (now.volume != Group::kDefaultVolume || now.mutedByMe); - if (now.videoParams - && now.ssrc - && (!was - || was->videoParams != now.videoParams - || was->ssrc != now.ssrc) - && (now.peer != _joinAs) - && (_instanceMode != InstanceMode::None)) { - prepareParticipantForAdding(now); - addPreparedParticipantsDelayed(); - } - if (volumeChanged) { _instance->setVolume( now.ssrc, @@ -630,8 +799,6 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { } }, _lifetime); - addParticipantsToInstance(); - _peer->session().updates().addActiveChat( _peerStream.events_starting_with_copy(_peer)); SubscribeToMigration(_peer, _lifetime, [=](not_null group) { @@ -744,13 +911,75 @@ void GroupCall::rejoin(not_null as) { }); } -[[nodiscard]] uint64 FindLocalRaisedHandRating( - const std::vector &list) { - const auto i = ranges::max_element( - list, - ranges::less(), - &Data::GroupCallParticipant::raisedHandRating); - return (i == end(list)) ? 1 : (i->raisedHandRating + 1); +void GroupCall::joinLeavePresentation() { + if (_screenOutgoing->state() == Webrtc::VideoState::Active) { + rejoinPresentation(); + } else { + leavePresentation(); + } +} + +void GroupCall::rejoinPresentation() { + _screenSsrc = 0; + ensureScreencastCreated(); + setScreenInstanceMode(InstanceMode::None); + LOG(("Call Info: Requesting join payload.")); + + const auto weak = base::make_weak(this); + _screenInstance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { + crl::on_main(weak, [=, payload = std::move(payload)]{ + const auto ssrc = payload.audioSsrc; + LOG(("Call Info: Join payload received, joining with ssrc: %1." + ).arg(ssrc)); + + const auto json = QByteArray::fromStdString(payload.json); + _api.request(MTPphone_JoinGroupCallPresentation( + inputCall(), + MTP_dataJSON(MTP_bytes(json)) + )).done([=](const MTPUpdates &updates) { + _screenSsrc = ssrc; + _mySsrcs.emplace(ssrc); + _peer->session().api().applyUpdates(updates); + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + LOG(("Call Error: " + "Could not screen join, error: %1").arg(type)); + + if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { + rejoinPresentation(); + return; + } + + //hangup(); + //Ui::ShowMultilineToast({ + // .text = { type == u"GROUPCALL_ANONYMOUS_FORBIDDEN"_q + // ? tr::lng_group_call_no_anonymous(tr::now) + // : type == u"GROUPCALL_PARTICIPANTS_TOO_MUCH"_q + // ? tr::lng_group_call_too_many(tr::now) + // : type == u"GROUPCALL_FORBIDDEN"_q + // ? tr::lng_group_not_accessible(tr::now) + // : Lang::Hard::ServerError() }, + //}); + }).send(); + }); + }); +} + +void GroupCall::leavePresentation() { + if (!_screenSsrc) { + return; + } + _api.request(MTPphone_LeaveGroupCallPresentation( + inputCall() + )).done([=](const MTPUpdates &updates) { + _screenSsrc = 0; + _peer->session().api().applyUpdates(updates); + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + LOG(("Call Error: " + "Could not screen leave, error: %1").arg(type)); + _screenSsrc = 0; + }).send(); } void GroupCall::applyMeInCallLocally() { @@ -775,13 +1004,14 @@ void GroupCall::applyMeInCallLocally() { : Group::kDefaultVolume; const auto canSelfUnmute = (muted() != MuteState::ForceMuted) && (muted() != MuteState::RaisedHand); - const auto videoMuted = (_videoOutgoing->state() - != Webrtc::VideoState::Active); const auto raisedHandRating = (muted() != MuteState::RaisedHand) ? uint64(0) : (i != end(participants)) ? i->raisedHandRating : FindLocalRaisedHandRating(participants); + const auto params = (i != end(participants)) + ? i->videoParams.get() + : nullptr; const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) | (lastActive ? Flag::f_active_date : Flag(0)) | (_mySsrc ? Flag(0) : Flag::f_left) @@ -789,7 +1019,10 @@ void GroupCall::applyMeInCallLocally() { | Flag::f_volume // Without flag the volume is reset to 100%. | Flag::f_volume_by_admin // Self volume can only be set by admin. | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) - //| (videoMuted ? Flag(0) : Flag::f_video) + | ((params && !params->camera.empty()) ? Flag::f_video : Flag(0)) + | ((params && !params->screen.empty()) + ? Flag::f_presentation + : Flag(0)) | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)); call->applyLocalUpdate( MTP_updateGroupCallParticipants( @@ -805,8 +1038,12 @@ void GroupCall::applyMeInCallLocally() { MTP_int(volume), MTPstring(), // Don't update about text in local updates. MTP_long(raisedHandRating), - MTPDataJSON(), // video - MTPDataJSON())), // presentation + (params + ? MTP_dataJSON(MTP_bytes(params->camera.json)) + : MTPDataJSON()), + (params + ? MTP_dataJSON(MTP_bytes(params->screen.json)) + : MTPDataJSON()))), MTP_int(0)).c_updateGroupCallParticipants()); } @@ -824,6 +1061,7 @@ void GroupCall::applyParticipantLocally( ? participant->canSelfUnmute : (!mute || IsGroupCallAdmin(_peer, participantPeer)); const auto isMutedByYou = mute && !canManageCall; + const auto params = participant->videoParams.get(); const auto mutedCount = 0/*participant->mutedCount*/; using Flag = MTPDgroupCallParticipant::Flag; const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) @@ -835,7 +1073,10 @@ void GroupCall::applyParticipantLocally( | (isMuted ? Flag::f_muted : Flag(0)) | (isMutedByYou ? Flag::f_muted_by_you : Flag(0)) | (participantPeer == _joinAs ? Flag::f_self : Flag(0)) - //| (participant->videoMuted ? Flag(0) : Flag::f_video) + | ((params && !params->camera.empty()) ? Flag::f_video : Flag(0)) + | ((params && !params->screen.empty()) + ? Flag::f_presentation + : Flag(0)) | (participant->raisedHandRating ? Flag::f_raise_hand_rating : Flag(0)); @@ -853,8 +1094,12 @@ void GroupCall::applyParticipantLocally( MTP_int(volume.value_or(participant->volume)), MTPstring(), // Don't update about text in local updates. MTP_long(participant->raisedHandRating), - MTPDataJSON(), // video - MTPDataJSON())), // presentation + (params + ? MTP_dataJSON(MTP_bytes(params->camera.json)) + : MTPDataJSON()), + (params + ? MTP_dataJSON(MTP_bytes(params->screen.json)) + : MTPDataJSON()))), MTP_int(0)).c_updateGroupCallParticipants()); } @@ -962,15 +1207,19 @@ void GroupCall::toggleScheduleStartSubscribed(bool subscribed) { } void GroupCall::addVideoOutput( - uint32 ssrc, + const std::string &endpointId, not_null track) { if (_instance) { - _instance->addIncomingVideoOutput(ssrc, track->sink()); + _instance->addIncomingVideoOutput(endpointId, track->sink()); } } -not_null GroupCall::outgoingVideoTrack() const { - return _videoOutgoing.get(); +not_null GroupCall::outgoingCameraTrack() const { + return _cameraOutgoing.get(); +} + +not_null GroupCall::outgoingScreenTrack() const { + return _screenOutgoing.get(); } void GroupCall::setMuted(MuteState mute) { @@ -1044,13 +1293,22 @@ void GroupCall::handlePossibleCreateOrJoinResponse( void GroupCall::handlePossibleCreateOrJoinResponse( const MTPDupdateGroupCallConnection &data) { if (data.is_presentation()) { - // #TODO calls - return; - } - data.vparams().match([&](const MTPDdataJSON &data) { + setScreenInstanceMode(InstanceMode::Rtc); + data.vparams().match([&](const MTPDdataJSON &data) { + const auto json = data.vdata().v; + _screenEndpoint = ParseVideoEndpoint(json); + if (!_screenEndpoint.empty() && _instance) { + _instance->setIgnoreVideoEndpointIds({ _screenEndpoint }); + } + _screenInstance->setJoinResponsePayload(json.toStdString()); + }); + } else { setInstanceMode(InstanceMode::Rtc); - _instance->setJoinResponsePayload(data.vdata().v.toStdString()); - }); + data.vparams().match([&](const MTPDdataJSON &data) { + _instance->setJoinResponsePayload(data.vdata().v.toStdString()); + }); + checkMediaChannelDescriptions(); + } } void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { @@ -1061,50 +1319,23 @@ void GroupCall::handlePossibleDiscarded(const MTPDgroupCallDiscarded &data) { } } -void GroupCall::addParticipantsToInstance() { +void GroupCall::checkMediaChannelDescriptions( + Fn resolved) { const auto real = lookupReal(); if (!real || (_instanceMode == InstanceMode::None)) { return; } - for (const auto &participant : real->participants()) { - prepareParticipantForAdding(participant); - } - addPreparedParticipants(); -} - -void GroupCall::prepareParticipantForAdding( - const Data::GroupCallParticipant &participant) { - _preparedParticipants.push_back(participant.videoParams - ? participant.videoParams->description - : tgcalls::GroupParticipantDescription()); - auto &added = _preparedParticipants.back(); - added.audioSsrc = participant.ssrc; - _unresolvedSsrcs.remove(added.audioSsrc); - //for (const auto &group : added.videoSourceGroups) { // #TODO calls - // for (const auto ssrc : group.ssrcs) { - // _unresolvedSsrcs.remove(ssrc); - // } - //} -} - -void GroupCall::addPreparedParticipants() { - _addPreparedParticipantsScheduled = false; - if (!_preparedParticipants.empty()) { - _instance->addParticipants(base::take(_preparedParticipants)); - } - if (const auto real = lookupReal()) { - if (!_unresolvedSsrcs.empty()) { - real->resolveParticipants(base::take(_unresolvedSsrcs)); + for (auto i = begin(_mediaChannelDescriptionses) + ; i != end(_mediaChannelDescriptionses);) { + if (mediaChannelDescriptionsFill(i->get(), resolved)) { + i = _mediaChannelDescriptionses.erase(i); + } else { + ++i; } } -} - -void GroupCall::addPreparedParticipantsDelayed() { - if (_addPreparedParticipantsScheduled) { - return; + if (!_unresolvedSsrcs.empty()) { + real->resolveParticipants(base::take(_unresolvedSsrcs)); } - _addPreparedParticipantsScheduled = true; - crl::on_main(this, [=] { addPreparedParticipants(); }); } void GroupCall::handleUpdate(const MTPUpdate &update) { @@ -1247,25 +1478,19 @@ void GroupCall::setupMediaDevices() { _mediaDevices->videoInputId( ) | rpl::start_with_next([=](QString id) { - const auto usedCamera = !isScreenSharing(); - _videoInputId = id; - if (_videoCapture && usedCamera) { - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + _cameraInputId = id; + if (_cameraCapture) { + _cameraCapture->switchToDevice(id.toStdString()); } }, _lifetime); setupOutgoingVideo(); } void GroupCall::setupOutgoingVideo() { - _videoDeviceId = _videoInputId; - static const auto hasDevices = [] { - return !Webrtc::GetVideoInputList().empty(); - }; - const auto started = _videoOutgoing->state(); - if (!hasDevices()) { - _videoOutgoing->setState(Webrtc::VideoState::Inactive); - } - _videoOutgoing->stateValue( + //static const auto hasDevices = [] { + // return !Webrtc::GetVideoInputList().empty(); + //}; + _cameraOutgoing->stateValue( ) | rpl::start_with_next([=](Webrtc::VideoState state) { //if (state != Webrtc::VideoState::Inactive && !hasDevices()) { //_errors.fire({ ErrorType::NoCamera }); // #TODO calls @@ -1278,20 +1503,46 @@ void GroupCall::setupOutgoingVideo() { /*} else */if (state != Webrtc::VideoState::Inactive) { // Paused not supported right now. Assert(state == Webrtc::VideoState::Active); - if (!_videoCapture) { - _videoCapture = _delegate->groupCallGetVideoCapture( - _videoDeviceId); - _videoCapture->setOutput(_videoOutgoing->sink()); + if (!_cameraCapture) { + _cameraCapture = _delegate->groupCallGetVideoCapture( + _cameraInputId); + _cameraCapture->setOutput(_cameraOutgoing->sink()); } else { - _videoCapture->switchToDevice(_videoDeviceId.toStdString()); + _cameraCapture->switchToDevice(_cameraInputId.toStdString()); } if (_instance) { - _instance->setVideoCapture(_videoCapture); + _instance->setVideoCapture(_cameraCapture); } - _videoCapture->setState(tgcalls::VideoState::Active); - } else if (_videoCapture) { - _videoCapture->setState(tgcalls::VideoState::Inactive); + _cameraCapture->setState(tgcalls::VideoState::Active); + } else if (_cameraCapture) { + _cameraCapture->setState(tgcalls::VideoState::Inactive); } + sendSelfUpdate(SendUpdateType::VideoMuted); + applyMeInCallLocally(); + }, _lifetime); + + _screenOutgoing->stateValue( + ) | rpl::start_with_next([=](Webrtc::VideoState state) { + if (state != Webrtc::VideoState::Inactive) { + // Paused not supported right now. + Assert(state == Webrtc::VideoState::Active); + if (!_screenCapture) { + _screenCapture = std::shared_ptr( + tgcalls::VideoCaptureInterface::Create( + tgcalls::StaticThreads::getThreads(), + _screenDeviceId.toStdString())); + _screenCapture->setOutput(_screenOutgoing->sink()); + } else { + _screenCapture->switchToDevice(_screenDeviceId.toStdString()); + } + if (_screenInstance) { + _screenInstance->setVideoCapture(_screenCapture); + } + _screenCapture->setState(tgcalls::VideoState::Active); + } else if (_screenCapture) { + _screenCapture->setState(tgcalls::VideoState::Inactive); + } + joinLeavePresentation(); }, _lifetime); } @@ -1374,17 +1625,11 @@ void GroupCall::ensureControllerCreated() { .initialOutputDeviceId = _audioOutputId.toStdString(), .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( settings.callAudioBackend()), - .videoCapture = _videoCapture, + .videoCapture = _cameraCapture, .incomingVideoSourcesUpdated = [=]( - const std::vector &ssrcs) { - crl::on_main(weak, [=] { - setVideoStreams(ssrcs); - }); - }, - .participantDescriptionsRequired = [=]( - const std::vector &ssrcs) { - crl::on_main(weak, [=] { - requestParticipantsInformation(ssrcs); + std::vector endpointIds) { + crl::on_main(weak, [=, endpoints = std::move(endpointIds)] { + setIncomingVideoStreams(endpoints); }); }, .requestBroadcastPart = [=]( @@ -1402,6 +1647,19 @@ void GroupCall::ensureControllerCreated() { return result; }, .videoContentType = tgcalls::VideoContentType::Generic, + .requestMediaChannelDescriptions = [=]( + const std::vector &ssrcs, + std::function &&)> done) { + auto result = std::make_shared( + weak, + ssrcs, + std::move(done)); + crl::on_main(weak, [=]() mutable { + mediaChannelDescriptionsStart(std::move(result)); + }); + return result; + }, }; if (Logs::DebugEnabled()) { auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); @@ -1421,10 +1679,11 @@ void GroupCall::ensureControllerCreated() { LOG(("Call Info: Creating group instance")); _instance = std::make_unique( std::move(descriptor)); - _videoStreamLarge.changes( - ) | rpl::start_with_next([=](uint32 ssrc) { - _instance->setFullSizeVideoSsrc(ssrc); - if (!ssrc) { + + _videoEndpointLarge.changes( + ) | rpl::start_with_next([=](const std::string &endpoint) { + _instance->setFullSizeVideoEndpointId(endpoint); + if (endpoint.empty()) { _videoLargeTrack = nullptr; _videoLargeTrackWrap = nullptr; return; @@ -1435,7 +1694,9 @@ void GroupCall::ensureControllerCreated() { } _videoLargeTrackWrap->sink = Webrtc::CreateProxySink( _videoLargeTrackWrap->track.sink()); - _instance->addIncomingVideoOutput(ssrc, _videoLargeTrackWrap->sink); + _instance->addIncomingVideoOutput( + endpoint, + _videoLargeTrackWrap->sink); }, _lifetime); updateInstanceMuteState(); @@ -1444,6 +1705,46 @@ void GroupCall::ensureControllerCreated() { //raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled()); } +void GroupCall::ensureScreencastCreated() { + if (_screenInstance) { + return; + } + //const auto &settings = Core::App().settings(); + + const auto weak = base::make_weak(this); + //const auto myLevel = std::make_shared(); + tgcalls::GroupInstanceDescriptor descriptor = { + .threads = tgcalls::StaticThreads::getThreads(), + .config = tgcalls::GroupConfig{ + }, + .networkStateUpdated = [=](tgcalls::GroupNetworkState networkState) { + crl::on_main(weak, [=] { + setScreenInstanceConnected(networkState); + }); + }, + .videoCapture = _screenCapture, + .videoContentType = tgcalls::VideoContentType::Screencast, + }; +// if (Logs::DebugEnabled()) { +// auto callLogFolder = cWorkingDir() + qsl("DebugLogs"); +// auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt"); +// auto callLogNative = QDir::toNativeSeparators(callLogPath); +//#ifdef Q_OS_WIN +// descriptor.config.logPath.data = callLogNative.toStdWString(); +//#else // Q_OS_WIN +// const auto callLogUtf = QFile::encodeName(callLogNative); +// descriptor.config.logPath.data.resize(callLogUtf.size()); +// ranges::copy(callLogUtf, descriptor.config.logPath.data.begin()); +//#endif // Q_OS_WIN +// QFile(callLogPath).remove(); +// QDir().mkpath(callLogFolder); +// } + + LOG(("Call Info: Creating group screen instance")); + _screenInstance = std::make_unique( + std::move(descriptor)); +} + void GroupCall::broadcastPartStart(std::shared_ptr task) { const auto raw = task.get(); const auto time = raw->time(); @@ -1509,108 +1810,162 @@ void GroupCall::broadcastPartStart(std::shared_ptr task) { void GroupCall::broadcastPartCancel(not_null task) { const auto i = _broadcastParts.find(task); - if (i != _broadcastParts.end()) { + if (i != end(_broadcastParts)) { _api.request(i->second.requestId).cancel(); _broadcastParts.erase(i); } } -void GroupCall::requestParticipantsInformation( - const std::vector &ssrcs) { +void GroupCall::mediaChannelDescriptionsStart( + std::shared_ptr task) { + const auto raw = task.get(); + const auto real = lookupReal(); if (!real || (_instanceMode == InstanceMode::None)) { - for (const auto ssrc : ssrcs) { + for (const auto ssrc : task->ssrcs()) { _unresolvedSsrcs.emplace(ssrc); } + _mediaChannelDescriptionses.emplace(std::move(task)); return; } - - const auto &existing = real->participants(); - for (const auto ssrc : ssrcs) { - const auto byAudio = real->participantPeerByAudioSsrc(ssrc); - const auto participantPeer = byAudio - ? byAudio - : real->participantPeerByVideoSsrc(ssrc); - if (!participantPeer) { - _unresolvedSsrcs.emplace(ssrc); - continue; - } - const auto i = ranges::find( - existing, - not_null{ participantPeer }, - &Data::GroupCall::Participant::peer); - Assert(i != end(existing)); - - prepareParticipantForAdding(*i); + if (!mediaChannelDescriptionsFill(task.get())) { + _mediaChannelDescriptionses.emplace(std::move(task)); + Assert(!_unresolvedSsrcs.empty()); + } + if (!_unresolvedSsrcs.empty()) { + real->resolveParticipants(base::take(_unresolvedSsrcs)); } - addPreparedParticipants(); } -void GroupCall::setVideoStreams(const std::vector &ssrcs) { - const auto large = _videoStreamLarge.current(); - auto newLarge = large; - if (large && !ranges::contains(ssrcs, large)) { - newLarge = 0; - _videoStreamPinned = 0; +bool GroupCall::mediaChannelDescriptionsFill( + not_null task, + Fn resolved) { + using Channel = tgcalls::MediaChannelDescription; + auto result = false; + const auto real = lookupReal(); + Assert(real != nullptr); + const auto &existing = real->participants(); + for (const auto ssrc : task->ssrcs()) { + const auto add = [&]( + std::optional channel, + bool screen = false) { + if (task->finishWithAdding(ssrc, std::move(channel), screen)) { + result = true; + } + }; + const auto addVideoChannel = [&]( + not_null participantPeer, + const auto field) { + const auto i = ranges::find( + existing, + participantPeer, + &Data::GroupCallParticipant::peer); + Assert(i != end(existing)); + Assert(i->videoParams != nullptr); + const auto ¶ms = i->videoParams.get()->*field; + Assert(!params.empty()); + add(Channel{ + .type = Channel::Type::Video, + .audioSsrc = i->ssrc, + .videoInformation = params.json.toStdString(), + }, (field == &ParticipantVideoParams::screen)); + }; + if (const auto byAudio = real->participantPeerByAudioSsrc(ssrc)) { + add(Channel{ + .type = Channel::Type::Audio, + .audioSsrc = ssrc, + }); + } else if (const auto byCamera + = real->participantPeerByCameraSsrc(ssrc)) { + addVideoChannel(byCamera, &ParticipantVideoParams::camera); + } else if (const auto byScreen + = real->participantPeerByScreenSsrc(ssrc)) { + addVideoChannel(byScreen, &ParticipantVideoParams::screen); + } else if (resolved(ssrc)) { + add(std::nullopt); + } else if (!resolved) { + _unresolvedSsrcs.emplace(ssrc); + } } - auto removed = _videoStreamSsrcs; - for (const auto ssrc : ssrcs) { - const auto i = removed.find(ssrc); - const auto videoMuted = _videoMuted.contains(ssrc); + return result; +} + +void GroupCall::mediaChannelDescriptionsCancel( + not_null task) { + const auto i = _mediaChannelDescriptionses.find(task.get()); + if (i != end(_mediaChannelDescriptionses)) { + _mediaChannelDescriptionses.erase(i); + } +} + +void GroupCall::setIncomingVideoStreams( + const std::vector &endpoints) { + const auto large = _videoEndpointLarge.current(); + auto newLarge = large; + if (!large.empty() && !ranges::contains(endpoints, large)) { + newLarge = _videoEndpointPinned = std::string(); + } + auto removed = _incomingVideoEndpoints; + for (const auto &endpoint : endpoints) { + const auto i = removed.find(endpoint); + const auto videoActive = _activeVideoEndpoints.contains(endpoint); if (i != end(removed)) { removed.erase(i); } else { - _videoStreamSsrcs.emplace(ssrc); - if (!videoMuted) { - _streamsVideoUpdated.fire({ ssrc, true }); + _incomingVideoEndpoints.emplace(endpoint); + if (videoActive) { + _streamsVideoUpdated.fire({ endpoint, true }); } } } - if (!newLarge) { - _videoStreamLarge = chooseLargeVideoSsrc(); + if (newLarge.empty()) { + _videoEndpointLarge = chooseLargeVideoEndpoint(); } - for (const auto ssrc : removed) { - if (!_videoMuted.contains(ssrc)) { - _streamsVideoUpdated.fire({ ssrc, false }); + for (const auto &endpoint : removed) { + if (_activeVideoEndpoints.contains(endpoint)) { + _streamsVideoUpdated.fire({ endpoint, false }); } } } -uint32 GroupCall::chooseLargeVideoSsrc() const { +std::string GroupCall::chooseLargeVideoEndpoint() const { const auto real = lookupReal(); if (!real) { - return 0; + return std::string(); } - auto anySsrc = uint32(0); - auto lastSpokeVoiceSsrc = uint32(0); - auto lastSpokeAnythingSsrc = uint32(0); + auto anyEndpoint = std::string(); + auto screenEndpoint = std::string(); + auto speakingEndpoint = std::string(); + auto soundingEndpoint = std::string(); const auto &participants = real->participants(); - for (const auto ssrc : _videoStreamSsrcs) { - if (_videoMuted.contains(ssrc)) { + for (const auto &endpoint : _incomingVideoEndpoints) { + if (!_activeVideoEndpoints.contains(endpoint)) { continue; } - const auto &participants = real->participants(); - const auto i = ranges::find( - participants, - ssrc, - &Data::GroupCallParticipant::ssrc); - if (i != end(participants)) { - if (!lastSpokeVoiceSsrc && i->speaking) { - lastSpokeVoiceSsrc = ssrc; + if (const auto participant = real->participantByEndpoint(endpoint)) { + if (screenEndpoint.empty() + && participant->videoParams->screen.endpoint == endpoint) { + screenEndpoint = endpoint; + break; } - if (!lastSpokeAnythingSsrc && i->sounding) { - lastSpokeAnythingSsrc = ssrc; + if (speakingEndpoint.empty() && participant->speaking) { + speakingEndpoint = endpoint; } - if (!anySsrc) { - anySsrc = ssrc; + if (soundingEndpoint.empty() && participant->sounding) { + soundingEndpoint = endpoint; + } + if (anyEndpoint.empty()) { + anyEndpoint = endpoint; } } } - return lastSpokeVoiceSsrc - ? lastSpokeVoiceSsrc - : lastSpokeAnythingSsrc - ? lastSpokeAnythingSsrc - : anySsrc; + return !screenEndpoint.empty() + ? screenEndpoint + : !speakingEndpoint.empty() + ? speakingEndpoint + : !soundingEndpoint.empty() + ? soundingEndpoint + : anyEndpoint; } void GroupCall::updateInstanceMuteState() { @@ -1731,8 +2086,8 @@ void GroupCall::checkJoined() { return; } auto sources = QVector(1, MTP_int(_mySsrc)); - if (_screencastSsrc) { - sources.push_back(MTP_int(_screencastSsrc)); + if (_screenSsrc) { + sources.push_back(MTP_int(_screenSsrc)); } _api.request(MTPphone_CheckGroupCall( inputCall(), @@ -1744,18 +2099,21 @@ void GroupCall::checkJoined() { } else if (state() == State::Connecting) { _checkJoinedTimer.callOnce(kCheckJoinedTimeout); } - if (_screencastSsrc - && !ranges::contains(result.v, MTP_int(_screencastSsrc))) { + if (_screenSsrc + && !ranges::contains(result.v, MTP_int(_screenSsrc)) + && isScreenSharing()) { LOG(("Call Info: " - "Rejoin presentation after _screencastSsrc not found.")); - // #TODO calls + "Screen rejoin after _screenSsrc not found.")); + rejoinPresentation(); } }).fail([=](const MTP::Error &error) { LOG(("Call Info: Full rejoin after error '%1' in checkGroupCall." ).arg(error.type())); rejoin(); - if (_screencastSsrc) { - // #TODO calls + if (_screenSsrc && isScreenSharing()) { + LOG(("Call Info: " + "Full screen rejoin after _screenSsrc not found.")); + rejoinPresentation(); } }).send(); } @@ -1792,6 +2150,22 @@ void GroupCall::setInstanceConnected( } } +void GroupCall::setScreenInstanceConnected( + tgcalls::GroupNetworkState networkState) { + const auto inTransit = networkState.isTransitioningFromBroadcastToRtc; + const auto screenInstanceState = !networkState.isConnected + ? InstanceState::Disconnected + : inTransit + ? InstanceState::TransitionToRtc + : InstanceState::Connected; + const auto connected = (screenInstanceState + != InstanceState::Disconnected); + if (_screenInstanceState.current() == screenInstanceState) { + return; + } + _screenInstanceState = screenInstanceState; +} + void GroupCall::checkFirstTimeJoined() { if (_hadJoinedState || state() != State::Joined) { return; @@ -1826,6 +2200,22 @@ void GroupCall::setInstanceMode(InstanceMode mode) { }(), true); } +void GroupCall::setScreenInstanceMode(InstanceMode mode) { + Expects(_screenInstance != nullptr); + + _screenInstanceMode = mode; + + using Mode = tgcalls::GroupConnectionMode; + _screenInstance->setConnectionMode([&] { + switch (_instanceMode) { + case InstanceMode::None: return Mode::GroupConnectionModeNone; + case InstanceMode::Rtc: return Mode::GroupConnectionModeRtc; + case InstanceMode::Stream: return Mode::GroupConnectionModeBroadcast; + } + Unexpected("Mode in GroupCall::setInstanceMode."); + }(), true); +} + void GroupCall::maybeSendMutedUpdate(MuteState previous) { // Send Active <-> !Active or ForceMuted <-> RaisedHand changes. const auto now = muted(); @@ -1856,7 +2246,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { MTP_bool(muted() != MuteState::Active), MTP_int(100000), // volume MTP_bool(muted() == MuteState::RaisedHand), - MTP_bool(_videoOutgoing->state() != Webrtc::VideoState::Active) + MTP_bool(_cameraOutgoing->state() != Webrtc::VideoState::Active) )).done([=](const MTPUpdates &result) { _updateMuteRequestId = 0; _peer->session().api().applyUpdates(result); @@ -1870,12 +2260,11 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { }).send(); } -void GroupCall::pinVideoStream(uint32 ssrc) { - if (!ssrc || streamsVideo(ssrc)) { - _videoStreamPinned = ssrc; - if (ssrc) { - _videoStreamLarge = ssrc; - } +void GroupCall::pinVideoEndpoint(const std::string &endpoint) { + if (endpoint.empty()) { + _videoEndpointPinned = endpoint; + } else if (streamsVideo(endpoint)) { + _videoEndpointLarge = _videoEndpointPinned = endpoint; } } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 1aa4ea44e..8a98f6341 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -75,18 +75,33 @@ struct LevelUpdate { }; struct StreamsVideoUpdate { - uint32 ssrc = 0; + std::string endpoint; bool streams = false; }; -struct VideoParams; +struct VideoParams { + base::flat_set ssrcs; + std::string endpoint; + QByteArray json; + uint32 hash = 0; -[[nodiscard]] std::shared_ptr ParseVideoParams( - const QByteArray &video, - const QByteArray &screencast, - const std::shared_ptr &existing); -[[nodiscard]] const base::flat_set &VideoSourcesFromParams( - const std::shared_ptr ¶ms); + [[nodiscard]] bool empty() const { + return endpoint.empty() || ssrcs.empty() || json.isEmpty(); + } + [[nodiscard]] explicit operator bool() const { + return !empty(); + } +}; + +struct ParticipantVideoParams { + VideoParams camera; + VideoParams screen; +}; + +[[nodiscard]] std::shared_ptr ParseVideoParams( + const QByteArray &camera, + const QByteArray &screen, + const std::shared_ptr &existing); class GroupCall final : public base::has_weak_ptr { public: @@ -154,8 +169,11 @@ public: void startScheduledNow(); void toggleScheduleStartSubscribed(bool subscribed); - void addVideoOutput(uint32 ssrc, not_null track); - [[nodiscard]] not_null outgoingVideoTrack() const; + void addVideoOutput( + const std::string &endpoint, + not_null track); + [[nodiscard]] not_null outgoingCameraTrack() const; + [[nodiscard]] not_null outgoingScreenTrack() const; void setMuted(MuteState mute); void setMutedAndUpdate(MuteState mute); @@ -213,20 +231,21 @@ public: -> rpl::producer { return _streamsVideoUpdated.events(); } - [[nodiscard]] bool streamsVideo(uint32 ssrc) const { - return ssrc - && _videoStreamSsrcs.contains(ssrc) - && !_videoMuted.contains(ssrc); + [[nodiscard]] bool streamsVideo(const std::string &endpoint) const { + return !endpoint.empty() + && _incomingVideoEndpoints.contains(endpoint) + && _activeVideoEndpoints.contains(endpoint); } - [[nodiscard]] uint32 videoStreamPinned() const { - return _videoStreamPinned; + [[nodiscard]] const std::string &videoEndpointPinned() const { + return _videoEndpointPinned; } - void pinVideoStream(uint32 ssrc); - [[nodiscard]] uint32 videoStreamLarge() const { - return _videoStreamLarge.current(); + void pinVideoEndpoint(const std::string &endpoint); + [[nodiscard]] std::string videoEndpointLarge() const { + return _videoEndpointLarge.current(); } - [[nodiscard]] rpl::producer videoStreamLargeValue() const { - return _videoStreamLarge.value(); + [[nodiscard]] auto videoEndpointLargeValue() const + -> rpl::producer { + return _videoEndpointLarge.value(); } [[nodiscard]] Webrtc::VideoTrack *videoLargeTrack() const { return _videoLargeTrack.current(); @@ -251,7 +270,7 @@ public: [[nodiscard]] bool isScreenSharing() const; [[nodiscard]] QString screenSharingDeviceId() const; void toggleVideo(bool active); - void switchToScreenSharing(const QString &uniqueId); + void toggleScreenSharing(std::optional uniqueId); void toggleMute(const Group::MuteRequest &data); void changeVolume(const Group::VolumeRequest &data); @@ -269,10 +288,15 @@ public: private: class LoadPartTask; + class MediaChannelDescriptionsTask; public: void broadcastPartStart(std::shared_ptr task); void broadcastPartCancel(not_null task); + void mediaChannelDescriptionsStart( + std::shared_ptr task); + void mediaChannelDescriptionsCancel( + not_null task); private: using GlobalShortcutValue = base::GlobalShortcutValue; @@ -299,12 +323,19 @@ private: VideoMuted, }; + [[nodiscard]] bool mediaChannelDescriptionsFill( + not_null task, + Fn resolved = nullptr); + void checkMediaChannelDescriptions(Fn resolved = nullptr); + void handlePossibleCreateOrJoinResponse(const MTPDgroupCall &data); void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); void ensureControllerCreated(); void destroyController(); + void ensureScreencastCreated(); + void destroyScreencast(); void setState(State state); void finish(FinishType type); @@ -319,10 +350,15 @@ private: void saveDefaultJoinAs(not_null as); void subscribeToReal(not_null real); void setScheduledDate(TimeId date); + void joinLeavePresentation(); + void rejoinPresentation(); + void leavePresentation(); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); void setInstanceConnected(tgcalls::GroupNetworkState networkState); void setInstanceMode(InstanceMode mode); + void setScreenInstanceConnected(tgcalls::GroupNetworkState networkState); + void setScreenInstanceMode(InstanceMode mode); void checkLastSpoke(); void pushToTalkCancel(); @@ -335,14 +371,8 @@ private: void stopConnectingSound(); void playConnectingSoundOnce(); - void requestParticipantsInformation(const std::vector &ssrcs); - void addParticipantsToInstance(); - void prepareParticipantForAdding( - const Data::GroupCallParticipant &participant); - void addPreparedParticipants(); - void addPreparedParticipantsDelayed(); - void setVideoStreams(const std::vector &ssrcs); - [[nodiscard]] uint32 chooseLargeVideoSsrc() const; + void setIncomingVideoStreams(const std::vector &endpoints); + [[nodiscard]] std::string chooseLargeVideoEndpoint() const; void editParticipant( not_null participantPeer, @@ -368,17 +398,15 @@ private: MTP::Sender _api; rpl::event_stream> _realChanges; rpl::variable _state = State::Creating; - rpl::variable _instanceState - = InstanceState::Disconnected; - bool _instanceTransitioning = false; - InstanceMode _instanceMode = InstanceMode::None; base::flat_set _unresolvedSsrcs; - std::vector _preparedParticipants; - bool _addPreparedParticipantsScheduled = false; bool _recordingStoppedByMe = false; MTP::DcId _broadcastDcId = 0; base::flat_map, LoadingPart> _broadcastParts; + base::flat_set< + std::shared_ptr< + MediaChannelDescriptionsTask>, + base::pointer_comparator> _mediaChannelDescriptionses; not_null _joinAs; std::vector> _possibleJoinAs; @@ -395,21 +423,35 @@ private: uint64 _id = 0; uint64 _accessHash = 0; uint32 _mySsrc = 0; - uint32 _screencastSsrc = 0; + uint32 _screenSsrc = 0; TimeId _scheduleDate = 0; base::flat_set _mySsrcs; mtpRequestId _createRequestId = 0; mtpRequestId _updateMuteRequestId = 0; + rpl::variable _instanceState + = InstanceState::Disconnected; + bool _instanceTransitioning = false; + InstanceMode _instanceMode = InstanceMode::None; std::unique_ptr _instance; - std::shared_ptr _videoCapture; - const std::unique_ptr _videoOutgoing; + std::shared_ptr _cameraCapture; + const std::unique_ptr _cameraOutgoing; + + rpl::variable _screenInstanceState + = InstanceState::Disconnected; + InstanceMode _screenInstanceMode = InstanceMode::None; + std::unique_ptr _screenInstance; + std::shared_ptr _screenCapture; + const std::unique_ptr _screenOutgoing; + QString _screenDeviceId; + std::string _screenEndpoint; + rpl::event_stream _levelUpdates; rpl::event_stream _streamsVideoUpdated; - base::flat_set _videoStreamSsrcs; - base::flat_set _videoMuted; - rpl::variable _videoStreamLarge = 0; - uint32 _videoStreamPinned = 0; + base::flat_set _incomingVideoEndpoints; + base::flat_set _activeVideoEndpoints; + rpl::variable _videoEndpointLarge; + std::string _videoEndpointPinned; std::unique_ptr _videoLargeTrackWrap; rpl::variable _videoLargeTrack; base::flat_map _lastSpoke; @@ -430,8 +472,7 @@ private: std::unique_ptr _mediaDevices; QString _audioInputId; QString _audioOutputId; - QString _videoInputId; - QString _videoDeviceId; + QString _cameraInputId; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index e61385081..59c1fe66c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -145,8 +145,10 @@ public: return _raisedHandRating; } - [[nodiscard]] not_null createVideoTrack(); + [[nodiscard]] not_null createVideoTrack( + const std::string &endpoint); void clearVideoTrack(); + [[nodiscard]] const std::string &videoTrackEndpoint() const; void setVideoTrack(not_null track); void addActionRipple(QPoint point, Fn updateCallback) override; @@ -276,6 +278,7 @@ private: std::unique_ptr _statusIcon; std::unique_ptr _videoTrack; Webrtc::VideoTrack *_videoTrackShown = nullptr; + std::string _videoTrackEndpoint; rpl::lifetime _videoTrackLifetime; // #TODO calls move to unique_ptr. Ui::Animations::Simple _speakingAnimation; // For gray-red/green icon. Ui::Animations::Simple _mutedAnimation; // For gray/red icon. @@ -380,14 +383,16 @@ private: not_null row, uint64 raiseHandRating) const; Row *findRow(not_null participantPeer) const; - Row *findRow(uint32 audioSsrc) const; + const Data::GroupCallParticipant *findParticipant( + const std::string &endpoint) const; + Row *findRow(const std::string &endpoint) const; void appendInvitedUsers(); void scheduleRaisedHandStatusRemove(); const not_null _call; not_null _peer; - uint32 _largeSsrc = 0; + std::string _largeEndpoint; bool _prepared = false; rpl::event_stream _toggleMuteRequests; @@ -1015,14 +1020,20 @@ void Row::refreshStatus() { _speaking); } -not_null Row::createVideoTrack() { +not_null Row::createVideoTrack( + const std::string &endpoint) { _videoTrackShown = nullptr; + _videoTrackEndpoint = endpoint; _videoTrack = std::make_unique( Webrtc::VideoState::Active); setVideoTrack(_videoTrack.get()); return _videoTrack.get(); } +const std::string &Row::videoTrackEndpoint() const { + return _videoTrackEndpoint; +} + void Row::clearVideoTrack() { _videoTrackLifetime.destroy(); _videoTrackShown = nullptr; @@ -1149,27 +1160,48 @@ void MembersController::setupListChangeViewers() { } }, _lifetime); - _call->videoStreamLargeValue( - ) | rpl::filter([=](uint32 largeSsrc) { - return (_largeSsrc != largeSsrc); - }) | rpl::start_with_next([=](uint32 largeSsrc) { - if (const auto row = findRow(_largeSsrc)) { - _call->addVideoOutput(_largeSsrc, row->createVideoTrack()); + _call->videoEndpointLargeValue( + ) | rpl::filter([=](const std::string &largeEndpoint) { + return (_largeEndpoint != largeEndpoint); + }) | rpl::start_with_next([=](const std::string &largeEndpoint) { + if (const auto participant = findParticipant(_largeEndpoint)) { + if (participant->cameraEndpoint() == _largeEndpoint) { + if (const auto row = findRow(participant->peer)) { + _call->addVideoOutput( + _largeEndpoint, + row->createVideoTrack(_largeEndpoint)); + } + } } - _largeSsrc = largeSsrc; - if (const auto row = findRow(_largeSsrc)) { - row->clearVideoTrack(); + _largeEndpoint = largeEndpoint; + if (const auto participant = findParticipant(_largeEndpoint)) { + if (participant->cameraEndpoint() == _largeEndpoint) { + if (const auto row = findRow(participant->peer)) { + row->clearVideoTrack(); + } + } } }, _lifetime); _call->streamsVideoUpdates( ) | rpl::start_with_next([=](StreamsVideoUpdate update) { - Assert(update.ssrc != _largeSsrc); - if (const auto row = findRow(update.ssrc)) { + Assert(update.endpoint != _largeEndpoint); + if (const auto participant = findParticipant(update.endpoint)) { if (update.streams) { - _call->addVideoOutput(update.ssrc, row->createVideoTrack()); + if (participant->cameraEndpoint() == update.endpoint + || !_call->streamsVideo(participant->cameraEndpoint())) { + if (const auto row = findRow(participant->peer)) { + _call->addVideoOutput( + update.endpoint, + row->createVideoTrack(update.endpoint)); + } + } } else { - row->clearVideoTrack(); + if (const auto row = findRow(participant->peer)) { + if (row->videoTrackEndpoint() == update.endpoint) { + row->clearVideoTrack(); + } + } } } }, _lifetime); @@ -1493,15 +1525,18 @@ Row *MembersController::findRow(not_null participantPeer) const { delegate()->peerListFindRow(participantPeer->id.value)); } -Row *MembersController::findRow(uint32 audioSsrc) const { - if (!audioSsrc) { +const Data::GroupCallParticipant *MembersController::findParticipant( + const std::string &endpoint) const { + if (endpoint.empty()) { return nullptr; } const auto real = _call->lookupReal(); - const auto participantPeer = real - ? real->participantPeerByAudioSsrc(audioSsrc) - : nullptr; - return participantPeer ? findRow(participantPeer) : nullptr; + return real ? real->participantByEndpoint(endpoint) : nullptr; +} + +Row *MembersController::findRow(const std::string &endpoint) const { + const auto participant = findParticipant(endpoint); + return participant ? findRow(participant->peer) : nullptr; } Main::Session &MembersController::session() const { @@ -1845,15 +1880,35 @@ base::unique_qptr MembersController::createRowContextMenu( _kickParticipantRequests.fire_copy(participantPeer); }); - const auto ssrc = real->ssrc(); - if (ssrc != 0 && _call->streamsVideo(ssrc)) { - const auto pinned = (_call->videoStreamPinned() == ssrc); - const auto phrase = pinned - ? tr::lng_group_call_context_unpin_video(tr::now) - : tr::lng_group_call_context_pin_video(tr::now); - result->addAction(phrase, [=] { - _call->pinVideoStream(pinned ? 0 : ssrc); - }); + if (const auto real = _call->lookupReal()) { + const auto pinnedEndpoint = _call->videoEndpointPinned(); + const auto participant = real->participantByEndpoint(pinnedEndpoint); + if (participant && participant->peer == participantPeer) { + result->addAction( + tr::lng_group_call_context_unpin_video(tr::now), + [=] { _call->pinVideoEndpoint(std::string()); }); + } else { + const auto &participants = real->participants(); + const auto i = ranges::find( + participants, + participantPeer, + &Data::GroupCallParticipant::peer); + if (i != end(participants)) { + const auto camera = i->cameraEndpoint(); + const auto screen = i->screenEndpoint(); + const auto streamsScreen = _call->streamsVideo(screen); + if (streamsScreen || _call->streamsVideo(camera)) { + const auto callback = [=] { + _call->pinVideoEndpoint(streamsScreen + ? screen + : camera); + }; + result->addAction( + tr::lng_group_call_context_pin_video(tr::now), + callback); + } + } + } } if (real->ssrc() != 0 @@ -2217,8 +2272,9 @@ void Members::setupPinnedVideo() { _mode.changes() | rpl::filter( _1 == PanelMode::Default ) | rpl::to_empty, - _call->videoStreamLargeValue() | rpl::filter([=](uint32 ssrc) { - return ssrc == _call->videoStreamPinned(); + _call->videoEndpointLargeValue( + ) | rpl::filter([=](const std::string &endpoint) { + return endpoint == _call->videoEndpointPinned(); }) | rpl::to_empty ) | rpl::start_with_next([=] { _scroll->scrollToY(0); diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index b0116ae32..732e1a970 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -493,11 +493,11 @@ rpl::lifetime &Panel::chooseSourceInstanceLifetime() { } void Panel::chooseSourceAccepted(const QString &deviceId) { - _call->switchToScreenSharing(deviceId); + _call->toggleScreenSharing(deviceId); } void Panel::chooseSourceStop() { - _call->toggleVideo(false); + _call->toggleScreenSharing(std::nullopt); } void Panel::initWindow() { @@ -722,10 +722,8 @@ void Panel::refreshLeftButton() { &st::groupCallVideoActiveSmall); _video->show(); _video->setClickedCallback([=] { - const auto sharing = _call->isScreenSharing(); - const auto active = (_call->outgoingVideoTrack()->state() - == Webrtc::VideoState::Active); - _call->toggleVideo(sharing || !active); + _call->toggleVideo(_call->outgoingCameraTrack()->state() + != Webrtc::VideoState::Active); }); _video->setText(tr::lng_group_call_video()); _video->setColorOverrides(_mute->colorOverrides()); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 98c56c1e4..1356866d5 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -33,8 +33,22 @@ constexpr auto kWaitForUpdatesTimeout = 3 * crl::time(1000); }); } +[[nodiscard]] const std::string &EmptyEndpoint() { + static const auto result = std::string(); + return result; +} + } // namespace + +const std::string &GroupCallParticipant::cameraEndpoint() const { + return videoParams ? videoParams->camera.endpoint : EmptyEndpoint(); +} + +const std::string &GroupCallParticipant::screenEndpoint() const { + return videoParams ? videoParams->screen.endpoint : EmptyEndpoint(); +} + GroupCall::GroupCall( not_null peer, uint64 id, @@ -193,13 +207,36 @@ PeerData *GroupCall::participantPeerByAudioSsrc(uint32 ssrc) const { : nullptr; } -PeerData *GroupCall::participantPeerByVideoSsrc(uint32 ssrc) const { - const auto i = _participantPeerByVideoSsrc.find(ssrc); - return (i != end(_participantPeerByVideoSsrc)) +PeerData *GroupCall::participantPeerByCameraSsrc(uint32 ssrc) const { + const auto i = _participantPeerByCameraSsrc.find(ssrc); + return (i != end(_participantPeerByCameraSsrc)) ? i->second.get() : nullptr; } +PeerData *GroupCall::participantPeerByScreenSsrc(uint32 ssrc) const { + const auto i = _participantPeerByScreenSsrc.find(ssrc); + return (i != end(_participantPeerByScreenSsrc)) + ? i->second.get() + : nullptr; +} + +const GroupCallParticipant *GroupCall::participantByEndpoint( + const std::string &endpoint) const { + if (endpoint.empty()) { + return nullptr; + } + for (const auto &participant : _participants) { + if (const auto params = participant.videoParams.get()) { + if (params->camera.endpoint == endpoint + || params->screen.endpoint == endpoint) { + return &participant; + } + } + } + return nullptr; +} + rpl::producer<> GroupCall::participantsSliceAdded() { return _participantsSliceAdded.events(); } @@ -305,7 +342,8 @@ void GroupCall::processFullCallFields(const MTPphone_GroupCall &call) { _participants.clear(); _speakingByActiveFinishes.clear(); _participantPeerByAudioSsrc.clear(); - _participantPeerByVideoSsrc.clear(); + _participantPeerByCameraSsrc.clear(); + _participantPeerByScreenSsrc.clear(); _allParticipantsLoaded = false; applyParticipantsSlice( @@ -499,10 +537,7 @@ void GroupCall::applyParticipantsSlice( .was = *i, }; _participantPeerByAudioSsrc.erase(i->ssrc); - const auto &all = VideoSourcesFromParams(i->videoParams); - for (const auto ssrc : all) { - _participantPeerByVideoSsrc.erase(ssrc); - } + eraseVideoSsrcs(*i); _speakingByActiveFinishes.remove(participantPeer); _participants.erase(i); if (sliceSource != ApplySliceSource::SliceLoaded) { @@ -543,8 +578,8 @@ void GroupCall::applyParticipantsSlice( && (!was || was->onlyMinLoaded); const auto raisedHandRating = data.vraise_hand_rating().value_or_empty(); - const auto hasVideoParamsInformation = (sliceSource - != ApplySliceSource::UpdateConstructed); + const auto hasVideoParamsInformation = true/*(sliceSource + != ApplySliceSource::UpdateConstructed)*/; const auto value = Participant{ .peer = participantPeer, .videoParams = (hasVideoParamsInformation @@ -571,19 +606,13 @@ void GroupCall::applyParticipantsSlice( .muted = data.is_muted(), .mutedByMe = mutedByMe, .canSelfUnmute = canSelfUnmute, - .videoMuted = (data.vvideo() == nullptr), .onlyMinLoaded = onlyMinLoaded, }; if (i == end(_participants)) { _participantPeerByAudioSsrc.emplace( value.ssrc, participantPeer); - const auto &all = VideoSourcesFromParams(value.videoParams); - for (const auto ssrc : all) { - _participantPeerByVideoSsrc.emplace( - ssrc, - participantPeer); - } + emplaceVideoSsrcs(value); _participants.push_back(value); if (const auto user = participantPeer->asUser()) { _peer->owner().unregisterInvitedToCallUser(_id, user); @@ -596,17 +625,8 @@ void GroupCall::applyParticipantsSlice( participantPeer); } if (i->videoParams != value.videoParams) { - const auto &old = VideoSourcesFromParams(i->videoParams); - for (const auto ssrc : old) { - _participantPeerByVideoSsrc.erase(ssrc); - } - const auto &now = VideoSourcesFromParams( - value.videoParams); - for (const auto ssrc : now) { - _participantPeerByVideoSsrc.emplace( - ssrc, - participantPeer); - } + eraseVideoSsrcs(*i); + emplaceVideoSsrcs(value); } *i = value; } @@ -627,6 +647,29 @@ void GroupCall::applyParticipantsSlice( } } +void GroupCall::emplaceVideoSsrcs(const Participant &participant) { + if (const auto params = participant.videoParams.get()) { + const auto participantPeer = participant.peer; + for (const auto ssrc : params->camera.ssrcs) { + _participantPeerByCameraSsrc.emplace(ssrc, participantPeer); + } + for (const auto ssrc : params->screen.ssrcs) { + _participantPeerByScreenSsrc.emplace(ssrc, participantPeer); + } + } +} + +void GroupCall::eraseVideoSsrcs(const Participant &participant) { + if (const auto params = participant.videoParams.get()) { + for (const auto ssrc : params->camera.ssrcs) { + _participantPeerByCameraSsrc.erase(ssrc); + } + for (const auto ssrc : params->screen.ssrcs) { + _participantPeerByScreenSsrc.erase(ssrc); + } + } +} + void GroupCall::applyLastSpoke( uint32 ssrc, LastSpokeTimes when, @@ -840,6 +883,9 @@ void GroupCall::requestUnknownParticipants() { } _unknownSpokenPeerIds.remove(id); } + if (!ssrcs.empty()) { + _participantsResolved.fire(&ssrcs); + } requestUnknownParticipants(); }).fail([=](const MTP::Error &error) { _unknownParticipantPeersRequestId = 0; diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 4a7a5965e..4015f239f 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -14,7 +14,7 @@ class PeerData; class ApiWrap; namespace Calls { -struct VideoParams; +struct ParticipantVideoParams; } // namespace Calls namespace Data { @@ -26,7 +26,7 @@ struct LastSpokeTimes { struct GroupCallParticipant { not_null peer; - std::shared_ptr videoParams; + std::shared_ptr videoParams; TimeId date = 0; TimeId lastActive = 0; uint64 raisedHandRating = 0; @@ -38,8 +38,10 @@ struct GroupCallParticipant { bool muted = false; bool mutedByMe = false; bool canSelfUnmute = false; - bool videoMuted = true; bool onlyMinLoaded = false; + + [[nodiscard]] const std::string &cameraEndpoint() const; + [[nodiscard]] const std::string &screenEndpoint() const; }; class GroupCall final { @@ -104,7 +106,10 @@ public: void requestParticipants(); [[nodiscard]] bool participantsLoaded() const; [[nodiscard]] PeerData *participantPeerByAudioSsrc(uint32 ssrc) const; - [[nodiscard]] PeerData *participantPeerByVideoSsrc(uint32 ssrc) const; + [[nodiscard]] PeerData *participantPeerByCameraSsrc(uint32 ssrc) const; + [[nodiscard]] PeerData *participantPeerByScreenSsrc(uint32 ssrc) const; + [[nodiscard]] const Participant *participantByEndpoint( + const std::string &endpoint) const; [[nodiscard]] rpl::producer<> participantsSliceAdded(); [[nodiscard]] rpl::producer participantUpdated() const; @@ -120,6 +125,12 @@ public: PeerData *participantPeerLoaded); void resolveParticipants(const base::flat_set &ssrcs); + [[nodiscard]] rpl::producer< + not_null*>> participantsResolved() const { + return _participantsResolved.events(); + } [[nodiscard]] int fullCount() const; [[nodiscard]] rpl::producer fullCountValue() const; @@ -167,6 +178,9 @@ private: void processSavedFullCall(); void finishParticipantsSliceRequest(); + void emplaceVideoSsrcs(const Participant &participant); + void eraseVideoSsrcs(const Participant &participant); + const uint64 _id = 0; const uint64 _accessHash = 0; @@ -184,7 +198,8 @@ private: std::vector _participants; base::flat_map> _participantPeerByAudioSsrc; - base::flat_map> _participantPeerByVideoSsrc; + base::flat_map> _participantPeerByCameraSsrc; + base::flat_map> _participantPeerByScreenSsrc; base::flat_map, crl::time> _speakingByActiveFinishes; base::Timer _speakingByActiveFinishTimer; QString _nextOffset; @@ -196,6 +211,10 @@ private: base::flat_map _unknownSpokenSsrcs; base::flat_map _unknownSpokenPeerIds; + rpl::event_stream< + not_null*>> _participantsResolved; mtpRequestId _unknownParticipantPeersRequestId = 0; rpl::event_stream _participantUpdates; diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 9928c00d2..697ef2ed6 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 9928c00d231c1194896d582a71e3bb6d70ee2765 +Subproject commit 697ef2ed67cfcad81b0e61caf0945a057a847327 From d9aa6602533416dc61566ae3eadd2fbc55eafbfc Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 30 Apr 2021 20:15:39 +0400 Subject: [PATCH 093/477] Handle members slice loaded. --- .../calls/group/calls_group_call.cpp | 19 +++++++++++++++++++ .../calls/group/calls_group_members.cpp | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index cdc22d9e4..ae1cddad0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -501,6 +501,25 @@ void GroupCall::subscribeToReal(not_null real) { const auto emptyEndpoint = std::string(); + real->participantsSliceAdded( + ) | rpl::start_with_next([=] { + const auto &participants = real->participants(); + for (const auto &participant : participants) { + const auto camera = participant.cameraEndpoint(); + const auto screen = participant.screenEndpoint(); + if (!camera.empty() + && _activeVideoEndpoints.emplace(camera).second + && _incomingVideoEndpoints.contains(camera)) { + _streamsVideoUpdated.fire({ camera, true }); + } + if (!screen.empty() + && _activeVideoEndpoints.emplace(screen).second + && _incomingVideoEndpoints.contains(screen)) { + _streamsVideoUpdated.fire({ screen, true }); + } + } + }, _lifetime); + using Update = Data::GroupCall::ParticipantUpdate; real->participantUpdated( ) | rpl::start_with_next([=](const Update &data) { diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 59c1fe66c..c1b766da8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -1175,8 +1175,8 @@ void MembersController::setupListChangeViewers() { } _largeEndpoint = largeEndpoint; if (const auto participant = findParticipant(_largeEndpoint)) { - if (participant->cameraEndpoint() == _largeEndpoint) { - if (const auto row = findRow(participant->peer)) { + if (const auto row = findRow(participant->peer)) { + if (row->videoTrackEndpoint() == _largeEndpoint) { row->clearVideoTrack(); } } From 2e400d88d3e0171eea9e9001a534bd92daadc2f6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 30 Apr 2021 22:04:12 +0400 Subject: [PATCH 094/477] Fix creating group calls. --- Telegram/SourceFiles/calls/calls.style | 2 +- .../calls/group/calls_group_call.cpp | 42 ++++++++++++------- .../calls/group/calls_group_call.h | 9 ++-- .../calls/group/calls_group_panel.cpp | 12 +++--- .../ui/desktop_capture_choose_source.cpp | 5 +-- 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index e346a43ee..afc113e1a 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1130,7 +1130,7 @@ desktopCaptureSubmit: RoundButton(desktopCaptureCancel) { textBgOver: groupCallMuted1; ripple: RippleAnimation(groupCallRipple) { - color: groupCallMuted2; + color: shadowFg; } } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index ae1cddad0..a3b5222e7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -379,10 +379,6 @@ GroupCall::GroupCall( , _joinHash(info.joinHash) , _id(inputCall.c_inputGroupCall().vid().v) , _scheduleDate(info.scheduleDate) -, _cameraOutgoing( - std::make_unique(Webrtc::VideoState::Inactive)) -, _screenOutgoing( - std::make_unique(Webrtc::VideoState::Inactive)) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) , _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) @@ -454,7 +450,13 @@ GroupCall::~GroupCall() { } bool GroupCall::isScreenSharing() const { - return (_screenOutgoing->state() == Webrtc::VideoState::Active); + return _screenOutgoing + && (_screenOutgoing->state() == Webrtc::VideoState::Active); +} + +bool GroupCall::isCameraSharing() const { + return _cameraOutgoing + && (_cameraOutgoing->state() == Webrtc::VideoState::Active); } QString GroupCall::screenSharingDeviceId() const { @@ -462,6 +464,10 @@ QString GroupCall::screenSharingDeviceId() const { } void GroupCall::toggleVideo(bool active) { + if (!_instance || !_id) { + return; + } + ensureOutgoingVideo(); const auto state = active ? Webrtc::VideoState::Active : Webrtc::VideoState::Inactive; @@ -471,6 +477,7 @@ void GroupCall::toggleVideo(bool active) { } void GroupCall::toggleScreenSharing(std::optional uniqueId) { + ensureOutgoingVideo(); if (!uniqueId) { _screenOutgoing->setState(Webrtc::VideoState::Inactive); return; @@ -1233,14 +1240,6 @@ void GroupCall::addVideoOutput( } } -not_null GroupCall::outgoingCameraTrack() const { - return _cameraOutgoing.get(); -} - -not_null GroupCall::outgoingScreenTrack() const { - return _screenOutgoing.get(); -} - void GroupCall::setMuted(MuteState mute) { const auto set = [=] { const auto wasMuted = (muted() == MuteState::Muted) @@ -1502,10 +1501,20 @@ void GroupCall::setupMediaDevices() { _cameraCapture->switchToDevice(id.toStdString()); } }, _lifetime); - setupOutgoingVideo(); } -void GroupCall::setupOutgoingVideo() { +void GroupCall::ensureOutgoingVideo() { + Expects(_id != 0); + + if (_cameraOutgoing) { + return; + } + + _cameraOutgoing = std::make_unique( + Webrtc::VideoState::Inactive); + _screenOutgoing = std::make_unique( + Webrtc::VideoState::Inactive); + //static const auto hasDevices = [] { // return !Webrtc::GetVideoInputList().empty(); //}; @@ -2265,7 +2274,8 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { MTP_bool(muted() != MuteState::Active), MTP_int(100000), // volume MTP_bool(muted() == MuteState::RaisedHand), - MTP_bool(_cameraOutgoing->state() != Webrtc::VideoState::Active) + MTP_bool(!_cameraOutgoing + || _cameraOutgoing->state() != Webrtc::VideoState::Active) )).done([=](const MTPUpdates &result) { _updateMuteRequestId = 0; _peer->session().api().applyUpdates(result); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 8a98f6341..65eed2165 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -172,8 +172,6 @@ public: void addVideoOutput( const std::string &endpoint, not_null track); - [[nodiscard]] not_null outgoingCameraTrack() const; - [[nodiscard]] not_null outgoingScreenTrack() const; void setMuted(MuteState mute); void setMutedAndUpdate(MuteState mute); @@ -268,6 +266,7 @@ public: void setCurrentAudioDevice(bool input, const QString &deviceId); void setCurrentVideoDevice(const QString &deviceId); [[nodiscard]] bool isScreenSharing() const; + [[nodiscard]] bool isCameraSharing() const; [[nodiscard]] QString screenSharingDeviceId() const; void toggleVideo(bool active); void toggleScreenSharing(std::optional uniqueId); @@ -387,7 +386,7 @@ private: void applyOtherParticipantUpdate(const MTPDgroupCallParticipant &data); void setupMediaDevices(); - void setupOutgoingVideo(); + void ensureOutgoingVideo(); [[nodiscard]] MTPInputGroupCall inputCall() const; @@ -435,14 +434,14 @@ private: InstanceMode _instanceMode = InstanceMode::None; std::unique_ptr _instance; std::shared_ptr _cameraCapture; - const std::unique_ptr _cameraOutgoing; + std::unique_ptr _cameraOutgoing; rpl::variable _screenInstanceState = InstanceState::Disconnected; InstanceMode _screenInstanceMode = InstanceMode::None; std::unique_ptr _screenInstance; std::shared_ptr _screenCapture; - const std::unique_ptr _screenOutgoing; + std::unique_ptr _screenOutgoing; QString _screenDeviceId; std::string _screenEndpoint; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 732e1a970..9dc9ed036 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -722,8 +722,7 @@ void Panel::refreshLeftButton() { &st::groupCallVideoActiveSmall); _video->show(); _video->setClickedCallback([=] { - _call->toggleVideo(_call->outgoingCameraTrack()->state() - != Webrtc::VideoState::Active); + _call->toggleVideo(!_call->isCameraSharing()); }); _video->setText(tr::lng_group_call_video()); _video->setColorOverrides(_mute->colorOverrides()); @@ -912,6 +911,9 @@ void Panel::setupMembers() { _startsWhen.destroy(); _members.create(widget(), _call); + setupPinnedVideo(); + + _members->setMode(_mode); _members->show(); _members->desiredHeightValue( @@ -946,8 +948,6 @@ void Panel::setupMembers() { addMembers(); } }, _callLifetime); - - setupPinnedVideo(); } void Panel::raiseControls() { @@ -1550,7 +1550,9 @@ bool Panel::updateMode() { if (_members) { _members->setMode(mode); } - _pinnedVideo->setVisible(mode == PanelMode::Wide); + if (_pinnedVideo) { + _pinnedVideo->setVisible(mode == PanelMode::Wide); + } refreshControlsBackground(); updateControlsGeometry(); return true; diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp index 0aed00c13..17d9af835 100644 --- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp +++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp @@ -198,7 +198,7 @@ void Source::paint() { _widget.paintRipple( p, { 0, 0 }, - _active ? &st::groupCallMuted2->c : nullptr); + _active ? &st::shadowFg->c : nullptr); const auto size = _preview ? _preview->track.frameSize() : QSize(); const auto factor = style::DevicePixelRatio(); @@ -230,9 +230,8 @@ void Source::setupPreview() { ) | rpl::start_with_next([=] { if (_preview->track.frameSize().isEmpty()) { _preview->track.markFrameShown(); - } else { - _widget.update(); } + _widget.update(); }, _preview->lifetime); } From 54c2769d8ad93d632be5c3c7a77d5f31c5b047e0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 3 May 2021 21:43:24 +0400 Subject: [PATCH 095/477] Improve voice /video chat members management. --- .../calls/group/calls_group_call.cpp | 379 +++++++++++++----- .../calls/group/calls_group_call.h | 29 +- .../calls/group/calls_group_members.cpp | 140 +++++-- .../calls/group/calls_group_panel.cpp | 2 +- Telegram/SourceFiles/data/data_group_call.cpp | 59 +-- Telegram/SourceFiles/data/data_group_call.h | 7 +- .../view/history_view_group_call_tracker.cpp | 2 +- 7 files changed, 457 insertions(+), 161 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index a3b5222e7..95e2af545 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -108,6 +108,11 @@ constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000); return video.value("endpoint").toString().toStdString(); } +[[nodiscard]] const std::string &EmptyString() { + static const auto result = std::string(); + return result; +} + } // namespace class GroupCall::LoadPartTask final : public tgcalls::BroadcastPartTask { @@ -173,6 +178,10 @@ struct GroupCall::LargeTrack { std::shared_ptr sink; }; +struct GroupCall::SinkPointer { + std::shared_ptr data; +}; + [[nodiscard]] bool IsGroupCallAdmin( not_null peer, not_null participantPeer) { @@ -447,20 +456,29 @@ GroupCall::GroupCall( GroupCall::~GroupCall() { destroyController(); + destroyScreencast(); } -bool GroupCall::isScreenSharing() const { +bool GroupCall::isSharingScreen() const { return _screenOutgoing && (_screenOutgoing->state() == Webrtc::VideoState::Active); } -bool GroupCall::isCameraSharing() const { +const std::string &GroupCall::screenSharingEndpoint() const { + return isSharingScreen() ? _screenEndpoint : EmptyString(); +} + +bool GroupCall::isSharingCamera() const { return _cameraOutgoing && (_cameraOutgoing->state() == Webrtc::VideoState::Active); } +const std::string &GroupCall::cameraSharingEndpoint() const { + return isSharingCamera() ? _cameraEndpoint : EmptyString(); +} + QString GroupCall::screenSharingDeviceId() const { - return isScreenSharing() ? _screenDeviceId : QString(); + return isSharingScreen() ? _screenDeviceId : QString(); } void GroupCall::toggleVideo(bool active) { @@ -506,26 +524,11 @@ void GroupCall::subscribeToReal(not_null real) { setScheduledDate(date); }, _lifetime); - const auto emptyEndpoint = std::string(); - - real->participantsSliceAdded( + real->participantsReloaded( ) | rpl::start_with_next([=] { - const auto &participants = real->participants(); - for (const auto &participant : participants) { - const auto camera = participant.cameraEndpoint(); - const auto screen = participant.screenEndpoint(); - if (!camera.empty() - && _activeVideoEndpoints.emplace(camera).second - && _incomingVideoEndpoints.contains(camera)) { - _streamsVideoUpdated.fire({ camera, true }); - } - if (!screen.empty() - && _activeVideoEndpoints.emplace(screen).second - && _incomingVideoEndpoints.contains(screen)) { - _streamsVideoUpdated.fire({ screen, true }); - } - } + fillActiveVideoEndpoints(); }, _lifetime); + fillActiveVideoEndpoints(); using Update = Data::GroupCall::ParticipantUpdate; real->participantUpdated( @@ -533,6 +536,14 @@ void GroupCall::subscribeToReal(not_null real) { auto newLarge = _videoEndpointLarge.current(); auto updateCameraNotStreams = std::string(); auto updateScreenNotStreams = std::string(); + const auto regularEndpoint = [&](const std::string &endpoint) + -> const std::string & { + return (endpoint.empty() + || endpoint == _cameraEndpoint + || endpoint == _screenEndpoint) + ? EmptyString() + : endpoint; + }; const auto guard = gsl::finally([&] { if (newLarge.empty()) { newLarge = chooseLargeVideoEndpoint(); @@ -549,14 +560,16 @@ void GroupCall::subscribeToReal(not_null real) { }); const auto &wasCameraEndpoint = (data.was && data.was->videoParams) - ? data.was->videoParams->camera.endpoint - : emptyEndpoint; + ? regularEndpoint(data.was->videoParams->camera.endpoint) + : EmptyString(); const auto &nowCameraEndpoint = (data.now && data.now->videoParams) - ? data.now->videoParams->camera.endpoint - : emptyEndpoint; + ? regularEndpoint(data.now->videoParams->camera.endpoint) + : EmptyString(); if (wasCameraEndpoint != nowCameraEndpoint) { if (!nowCameraEndpoint.empty() - && _activeVideoEndpoints.emplace(nowCameraEndpoint).second + && _activeVideoEndpoints.emplace( + nowCameraEndpoint, + EndpointType::Camera).second && _incomingVideoEndpoints.contains(nowCameraEndpoint)) { _streamsVideoUpdated.fire({ nowCameraEndpoint, true }); } @@ -565,19 +578,21 @@ void GroupCall::subscribeToReal(not_null real) { && _incomingVideoEndpoints.contains(wasCameraEndpoint)) { updateCameraNotStreams = wasCameraEndpoint; if (newLarge == wasCameraEndpoint) { - newLarge = std::string(); + _videoEndpointPinned = newLarge = std::string(); } } } const auto &wasScreenEndpoint = (data.was && data.was->videoParams) ? data.was->videoParams->screen.endpoint - : emptyEndpoint; + : EmptyString(); const auto &nowScreenEndpoint = (data.now && data.now->videoParams) ? data.now->videoParams->screen.endpoint - : emptyEndpoint; + : EmptyString(); if (wasScreenEndpoint != nowScreenEndpoint) { if (!nowScreenEndpoint.empty() - && _activeVideoEndpoints.emplace(nowScreenEndpoint).second + && _activeVideoEndpoints.emplace( + nowScreenEndpoint, + EndpointType::Screen).second && _incomingVideoEndpoints.contains(nowScreenEndpoint)) { _streamsVideoUpdated.fire({ nowScreenEndpoint, true }); } @@ -586,7 +601,7 @@ void GroupCall::subscribeToReal(not_null real) { && _incomingVideoEndpoints.contains(wasScreenEndpoint)) { updateScreenNotStreams = wasScreenEndpoint; if (newLarge == wasScreenEndpoint) { - newLarge = std::string(); + _videoEndpointPinned = newLarge = std::string(); } } } @@ -600,7 +615,8 @@ void GroupCall::subscribeToReal(not_null real) { return; } if (nowScreenEndpoint != newLarge - && streamsVideo(nowScreenEndpoint)) { + && streamsVideo(nowScreenEndpoint) + && activeVideoEndpointType(newLarge) != EndpointType::Screen) { newLarge = nowScreenEndpoint; } const auto &participants = real->participants(); @@ -639,8 +655,8 @@ void GroupCall::subscribeToReal(not_null real) { newLarge = soundingEndpoint; } } else if ((nowSpeaking || nowSounding) - && (nowScreenEndpoint != newLarge) && (nowCameraEndpoint != newLarge) + && (activeVideoEndpointType(newLarge) != EndpointType::Screen) && streamsVideo(nowCameraEndpoint)) { const auto participant = real->participantByEndpoint(newLarge); const auto screen = participant @@ -701,6 +717,7 @@ void GroupCall::setState(State state) { // Destroy controller before destroying Call Panel, // so that the panel hide animation is smooth. destroyController(); + destroyScreencast(); } switch (state) { case State::HangingUp: @@ -833,6 +850,90 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) { }); } +void GroupCall::setMyEndpointType( + const std::string &endpoint, + EndpointType type) { + if (endpoint.empty()) { + return; + } else if (type == EndpointType::None) { + const auto was1 = _incomingVideoEndpoints.remove(endpoint); + const auto was2 = _activeVideoEndpoints.remove(endpoint); + if (was1 && was2) { + auto newLarge = _videoEndpointLarge.current(); + if (newLarge == endpoint) { + _videoEndpointPinned = std::string(); + _videoEndpointLarge = chooseLargeVideoEndpoint(); + } + _streamsVideoUpdated.fire({ endpoint, false }); + } + } else { + const auto now1 = _incomingVideoEndpoints.emplace(endpoint).second; + const auto now2 = _activeVideoEndpoints.emplace( + endpoint, + type).second; + if (now1 && now2) { + _streamsVideoUpdated.fire({ endpoint, true }); + } + const auto nowLarge = activeVideoEndpointType( + _videoEndpointLarge.current()); + if (_videoEndpointPinned.empty() + && ((type == EndpointType::Screen + && nowLarge != EndpointType::Screen) + || (type == EndpointType::Camera + && nowLarge == EndpointType::None))) { + _videoEndpointLarge = endpoint; + } + } +} + +void GroupCall::setScreenEndpoint(std::string endpoint) { + if (_screenEndpoint == endpoint) { + return; + } + if (!_screenEndpoint.empty()) { + setMyEndpointType(_screenEndpoint, EndpointType::None); + } + _screenEndpoint = std::move(endpoint); + if (_screenEndpoint.empty()) { + return; + } + if (_instance) { + _instance->setIgnoreVideoEndpointIds({ _screenEndpoint }); + } + if (isSharingScreen()) { + setMyEndpointType(_screenEndpoint, EndpointType::Screen); + } +} + +void GroupCall::setCameraEndpoint(std::string endpoint) { + if (_cameraEndpoint == endpoint) { + return; + } + if (!_cameraEndpoint.empty()) { + setMyEndpointType(_cameraEndpoint, EndpointType::None); + } + _cameraEndpoint = std::move(endpoint); + if (_cameraEndpoint.empty()) { + return; + } + if (isSharingCamera()) { + setMyEndpointType(_cameraEndpoint, EndpointType::Camera); + } +} + +void GroupCall::addVideoOutput( + const std::string &endpoint, + SinkPointer sink) { + if (_cameraEndpoint == endpoint) { + _cameraCapture->setOutput(sink.data); + } else if (_screenEndpoint == endpoint) { + _screenCapture->setOutput(sink.data); + } else { + Assert(_instance != nullptr); + _instance->addIncomingVideoOutput(endpoint, sink.data); + } +} + void GroupCall::rejoin() { rejoin(_joinAs); } @@ -913,6 +1014,10 @@ void GroupCall::rejoin(not_null as) { applyQueuedSelfUpdates(); checkFirstTimeJoined(); sendSelfUpdate(SendUpdateType::VideoMuted); + if (_screenSsrc && isSharingScreen()) { + LOG(("Call Info: Screen rejoin after rejoin().")); + rejoinPresentation(); + } }).fail([=](const MTP::Error &error) { const auto type = error.type(); LOG(("Call Error: Could not join, error: %1").arg(type)); @@ -970,22 +1075,16 @@ void GroupCall::rejoinPresentation() { const auto type = error.type(); LOG(("Call Error: " "Could not screen join, error: %1").arg(type)); - if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { rejoinPresentation(); - return; + } else if (type == u"GROUPCALL_JOIN_MISSING"_q + || type == u"GROUPCALL_FORBIDDEN"_q) { + _screenSsrc = ssrc; + rejoin(); + } else { + _screenSsrc = 0; + setScreenEndpoint(std::string()); } - - //hangup(); - //Ui::ShowMultilineToast({ - // .text = { type == u"GROUPCALL_ANONYMOUS_FORBIDDEN"_q - // ? tr::lng_group_call_no_anonymous(tr::now) - // : type == u"GROUPCALL_PARTICIPANTS_TOO_MUCH"_q - // ? tr::lng_group_call_too_many(tr::now) - // : type == u"GROUPCALL_FORBIDDEN"_q - // ? tr::lng_group_not_accessible(tr::now) - // : Lang::Hard::ServerError() }, - //}); }).send(); }); }); @@ -999,12 +1098,14 @@ void GroupCall::leavePresentation() { inputCall() )).done([=](const MTPUpdates &updates) { _screenSsrc = 0; + setScreenEndpoint(std::string()); _peer->session().api().applyUpdates(updates); }).fail([=](const MTP::Error &error) { const auto type = error.type(); LOG(("Call Error: " "Could not screen leave, error: %1").arg(type)); _screenSsrc = 0; + setScreenEndpoint(std::string()); }).send(); } @@ -1233,11 +1334,9 @@ void GroupCall::toggleScheduleStartSubscribed(bool subscribed) { } void GroupCall::addVideoOutput( - const std::string &endpointId, + const std::string &endpoint, not_null track) { - if (_instance) { - _instance->addIncomingVideoOutput(endpointId, track->sink()); - } + addVideoOutput(endpoint, { track->sink() }); } void GroupCall::setMuted(MuteState mute) { @@ -1314,16 +1413,15 @@ void GroupCall::handlePossibleCreateOrJoinResponse( setScreenInstanceMode(InstanceMode::Rtc); data.vparams().match([&](const MTPDdataJSON &data) { const auto json = data.vdata().v; - _screenEndpoint = ParseVideoEndpoint(json); - if (!_screenEndpoint.empty() && _instance) { - _instance->setIgnoreVideoEndpointIds({ _screenEndpoint }); - } + setScreenEndpoint(ParseVideoEndpoint(json)); _screenInstance->setJoinResponsePayload(json.toStdString()); }); } else { setInstanceMode(InstanceMode::Rtc); data.vparams().match([&](const MTPDdataJSON &data) { - _instance->setJoinResponsePayload(data.vdata().v.toStdString()); + const auto json = data.vdata().v; + setCameraEndpoint(ParseVideoEndpoint(json)); + _instance->setJoinResponsePayload(json.toStdString()); }); checkMediaChannelDescriptions(); } @@ -1534,7 +1632,6 @@ void GroupCall::ensureOutgoingVideo() { if (!_cameraCapture) { _cameraCapture = _delegate->groupCallGetVideoCapture( _cameraInputId); - _cameraCapture->setOutput(_cameraOutgoing->sink()); } else { _cameraCapture->switchToDevice(_cameraInputId.toStdString()); } @@ -1542,8 +1639,12 @@ void GroupCall::ensureOutgoingVideo() { _instance->setVideoCapture(_cameraCapture); } _cameraCapture->setState(tgcalls::VideoState::Active); - } else if (_cameraCapture) { - _cameraCapture->setState(tgcalls::VideoState::Inactive); + setMyEndpointType(_cameraEndpoint, EndpointType::Camera); + } else { + if (_cameraCapture) { + _cameraCapture->setState(tgcalls::VideoState::Inactive); + } + setMyEndpointType(_cameraEndpoint, EndpointType::None); } sendSelfUpdate(SendUpdateType::VideoMuted); applyMeInCallLocally(); @@ -1559,7 +1660,6 @@ void GroupCall::ensureOutgoingVideo() { tgcalls::VideoCaptureInterface::Create( tgcalls::StaticThreads::getThreads(), _screenDeviceId.toStdString())); - _screenCapture->setOutput(_screenOutgoing->sink()); } else { _screenCapture->switchToDevice(_screenDeviceId.toStdString()); } @@ -1567,8 +1667,12 @@ void GroupCall::ensureOutgoingVideo() { _screenInstance->setVideoCapture(_screenCapture); } _screenCapture->setState(tgcalls::VideoState::Active); - } else if (_screenCapture) { - _screenCapture->setState(tgcalls::VideoState::Inactive); + setMyEndpointType(_screenEndpoint, EndpointType::Screen); + } else { + if (_screenCapture) { + _screenCapture->setState(tgcalls::VideoState::Inactive); + } + setMyEndpointType(_screenEndpoint, EndpointType::None); } joinLeavePresentation(); }, _lifetime); @@ -1657,7 +1761,7 @@ void GroupCall::ensureControllerCreated() { .incomingVideoSourcesUpdated = [=]( std::vector endpointIds) { crl::on_main(weak, [=, endpoints = std::move(endpointIds)] { - setIncomingVideoStreams(endpoints); + setIncomingVideoEndpoints(endpoints); }); }, .requestBroadcastPart = [=]( @@ -1722,14 +1826,15 @@ void GroupCall::ensureControllerCreated() { } _videoLargeTrackWrap->sink = Webrtc::CreateProxySink( _videoLargeTrackWrap->track.sink()); - _instance->addIncomingVideoOutput( - endpoint, - _videoLargeTrackWrap->sink); + addVideoOutput(endpoint, { _videoLargeTrackWrap->sink }); }, _lifetime); updateInstanceMuteState(); updateInstanceVolumes(); + if (!_screenEndpoint.empty()) { + _instance->setIgnoreVideoEndpointIds({ _screenEndpoint }); + } //raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled()); } @@ -1909,10 +2014,10 @@ bool GroupCall::mediaChannelDescriptionsFill( } else if (const auto byScreen = real->participantPeerByScreenSsrc(ssrc)) { addVideoChannel(byScreen, &ParticipantVideoParams::screen); - } else if (resolved(ssrc)) { - add(std::nullopt); } else if (!resolved) { _unresolvedSsrcs.emplace(ssrc); + } else if (resolved(ssrc)) { + add(std::nullopt); } } return result; @@ -1926,25 +2031,33 @@ void GroupCall::mediaChannelDescriptionsCancel( } } -void GroupCall::setIncomingVideoStreams( +void GroupCall::setIncomingVideoEndpoints( const std::vector &endpoints) { - const auto large = _videoEndpointLarge.current(); - auto newLarge = large; - if (!large.empty() && !ranges::contains(endpoints, large)) { - newLarge = _videoEndpointPinned = std::string(); - } + auto newLarge = _videoEndpointLarge.current(); + auto newLargeFound = false; auto removed = _incomingVideoEndpoints; - for (const auto &endpoint : endpoints) { - const auto i = removed.find(endpoint); - const auto videoActive = _activeVideoEndpoints.contains(endpoint); - if (i != end(removed)) { - removed.erase(i); - } else { + const auto feedOne = [&](const std::string &endpoint) { + if (endpoint.empty()) { + return; + } else if (endpoint == newLarge) { + newLargeFound = true; + } + if (!removed.remove(endpoint)) { _incomingVideoEndpoints.emplace(endpoint); - if (videoActive) { + if (_activeVideoEndpoints.contains(endpoint)) { _streamsVideoUpdated.fire({ endpoint, true }); } } + }; + for (const auto &endpoint : endpoints) { + if (endpoint != _cameraEndpoint && endpoint != _screenEndpoint) { + feedOne(endpoint); + } + } + feedOne(cameraSharingEndpoint()); + feedOne(screenSharingEndpoint()); + if (!newLarge.empty() && !newLargeFound) { + newLarge = _videoEndpointPinned = std::string(); } if (newLarge.empty()) { _videoEndpointLarge = chooseLargeVideoEndpoint(); @@ -1956,6 +2069,65 @@ void GroupCall::setIncomingVideoStreams( } } +void GroupCall::fillActiveVideoEndpoints() { + const auto real = lookupReal(); + Assert(real != nullptr); + + const auto &participants = real->participants(); + auto newLarge = _videoEndpointLarge.current(); + auto newLargeFound = false; + auto removed = _activeVideoEndpoints; + const auto feedOne = [&]( + const std::string &endpoint, + EndpointType type) { + if (endpoint.empty()) { + return; + } else if (endpoint == newLarge) { + newLargeFound = true; + } + if (!removed.remove(endpoint)) { + _activeVideoEndpoints.emplace(endpoint, type); + if (_incomingVideoEndpoints.contains(endpoint)) { + _streamsVideoUpdated.fire({ endpoint, true }); + } + } + }; + for (const auto &participant : participants) { + const auto camera = participant.cameraEndpoint(); + if (camera != _cameraEndpoint && camera != _screenEndpoint) { + feedOne(camera, EndpointType::Camera); + } + const auto screen = participant.screenEndpoint(); + if (screen != _cameraEndpoint && screen != _screenEndpoint) { + feedOne(screen, EndpointType::Screen); + } + } + feedOne(cameraSharingEndpoint(), EndpointType::Camera); + feedOne(screenSharingEndpoint(), EndpointType::Screen); + if (!newLarge.empty() && !newLargeFound) { + newLarge = _videoEndpointPinned = std::string(); + } + if (newLarge.empty()) { + _videoEndpointLarge = chooseLargeVideoEndpoint(); + } + for (const auto &[endpoint, type] : removed) { + if (_activeVideoEndpoints.remove(endpoint)) { + _streamsVideoUpdated.fire({ endpoint, false }); + } + } +} + +GroupCall::EndpointType GroupCall::activeVideoEndpointType( + const std::string &endpoint) const { + if (endpoint.empty()) { + return EndpointType::None; + } + const auto i = _activeVideoEndpoints.find(endpoint); + return (i != end(_activeVideoEndpoints)) + ? i->second + : EndpointType::None; +} + std::string GroupCall::chooseLargeVideoEndpoint() const { const auto real = lookupReal(); if (!real) { @@ -1965,9 +2137,13 @@ std::string GroupCall::chooseLargeVideoEndpoint() const { auto screenEndpoint = std::string(); auto speakingEndpoint = std::string(); auto soundingEndpoint = std::string(); + const auto &myCameraEndpoint = cameraSharingEndpoint(); + const auto &myScreenEndpoint = screenSharingEndpoint(); const auto &participants = real->participants(); for (const auto &endpoint : _incomingVideoEndpoints) { - if (!_activeVideoEndpoints.contains(endpoint)) { + if (!_activeVideoEndpoints.contains(endpoint) + || endpoint == _cameraEndpoint + || endpoint == _screenEndpoint) { continue; } if (const auto participant = real->participantByEndpoint(endpoint)) { @@ -1989,11 +2165,17 @@ std::string GroupCall::chooseLargeVideoEndpoint() const { } return !screenEndpoint.empty() ? screenEndpoint + : streamsVideo(myScreenEndpoint) + ? myScreenEndpoint : !speakingEndpoint.empty() ? speakingEndpoint : !soundingEndpoint.empty() ? soundingEndpoint - : anyEndpoint; + : !anyEndpoint.empty() + ? anyEndpoint + : streamsVideo(myCameraEndpoint) + ? myCameraEndpoint + : std::string(); } void GroupCall::updateInstanceMuteState() { @@ -2124,25 +2306,22 @@ void GroupCall::checkJoined() { if (!ranges::contains(result.v, MTP_int(_mySsrc))) { LOG(("Call Info: Rejoin after no _mySsrc in checkGroupCall.")); rejoin(); - } else if (state() == State::Connecting) { - _checkJoinedTimer.callOnce(kCheckJoinedTimeout); - } - if (_screenSsrc - && !ranges::contains(result.v, MTP_int(_screenSsrc)) - && isScreenSharing()) { - LOG(("Call Info: " - "Screen rejoin after _screenSsrc not found.")); - rejoinPresentation(); + } else { + if (state() == State::Connecting) { + _checkJoinedTimer.callOnce(kCheckJoinedTimeout); + } + if (_screenSsrc + && !ranges::contains(result.v, MTP_int(_screenSsrc)) + && isSharingScreen()) { + LOG(("Call Info: " + "Screen rejoin after _screenSsrc not found.")); + rejoinPresentation(); + } } }).fail([=](const MTP::Error &error) { LOG(("Call Info: Full rejoin after error '%1' in checkGroupCall." ).arg(error.type())); rejoin(); - if (_screenSsrc && isScreenSharing()) { - LOG(("Call Info: " - "Full screen rejoin after _screenSsrc not found.")); - rejoinPresentation(); - } }).send(); } @@ -2493,4 +2672,12 @@ void GroupCall::destroyController() { } } +void GroupCall::destroyScreencast() { + if (_screenInstance) { + DEBUG_LOG(("Call Info: Destroying call screen controller..")); + _screenInstance.reset(); + DEBUG_LOG(("Call Info: Call screen controller destroyed.")); + } +} + } // namespace Calls diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 65eed2165..7c5e916e0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -232,7 +232,7 @@ public: [[nodiscard]] bool streamsVideo(const std::string &endpoint) const { return !endpoint.empty() && _incomingVideoEndpoints.contains(endpoint) - && _activeVideoEndpoints.contains(endpoint); + && activeVideoEndpointType(endpoint) != EndpointType::None; } [[nodiscard]] const std::string &videoEndpointPinned() const { return _videoEndpointPinned; @@ -265,8 +265,10 @@ public: void setCurrentAudioDevice(bool input, const QString &deviceId); void setCurrentVideoDevice(const QString &deviceId); - [[nodiscard]] bool isScreenSharing() const; - [[nodiscard]] bool isCameraSharing() const; + [[nodiscard]] bool isSharingScreen() const; + [[nodiscard]] const std::string &screenSharingEndpoint() const; + [[nodiscard]] bool isSharingCamera() const; + [[nodiscard]] const std::string &cameraSharingEndpoint() const; [[nodiscard]] QString screenSharingDeviceId() const; void toggleVideo(bool active); void toggleScreenSharing(std::optional uniqueId); @@ -300,6 +302,7 @@ public: private: using GlobalShortcutValue = base::GlobalShortcutValue; struct LargeTrack; + struct SinkPointer; struct LoadingPart { std::shared_ptr task; @@ -321,6 +324,11 @@ private: RaiseHand, VideoMuted, }; + enum class EndpointType { + None, + Camera, + Screen, + }; [[nodiscard]] bool mediaChannelDescriptionsFill( not_null task, @@ -370,8 +378,12 @@ private: void stopConnectingSound(); void playConnectingSoundOnce(); - void setIncomingVideoStreams(const std::vector &endpoints); + void setIncomingVideoEndpoints( + const std::vector &endpoints); + void fillActiveVideoEndpoints(); [[nodiscard]] std::string chooseLargeVideoEndpoint() const; + [[nodiscard]] EndpointType activeVideoEndpointType( + const std::string &endpoint) const; void editParticipant( not_null participantPeer, @@ -387,6 +399,10 @@ private: void setupMediaDevices(); void ensureOutgoingVideo(); + void setMyEndpointType(const std::string &endpoint, EndpointType type); + void setScreenEndpoint(std::string endpoint); + void setCameraEndpoint(std::string endpoint); + void addVideoOutput(const std::string &endpoint, SinkPointer sink); [[nodiscard]] MTPInputGroupCall inputCall() const; @@ -423,6 +439,8 @@ private: uint64 _accessHash = 0; uint32 _mySsrc = 0; uint32 _screenSsrc = 0; + std::string _cameraEndpoint; + std::string _screenEndpoint; TimeId _scheduleDate = 0; base::flat_set _mySsrcs; mtpRequestId _createRequestId = 0; @@ -443,12 +461,11 @@ private: std::shared_ptr _screenCapture; std::unique_ptr _screenOutgoing; QString _screenDeviceId; - std::string _screenEndpoint; rpl::event_stream _levelUpdates; rpl::event_stream _streamsVideoUpdated; base::flat_set _incomingVideoEndpoints; - base::flat_set _activeVideoEndpoints; + base::flat_map _activeVideoEndpoints; rpl::variable _videoEndpointLarge; std::string _videoEndpointPinned; std::unique_ptr _videoLargeTrackWrap; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index c1b766da8..2a8f7487a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -385,7 +385,13 @@ private: Row *findRow(not_null participantPeer) const; const Data::GroupCallParticipant *findParticipant( const std::string &endpoint) const; - Row *findRow(const std::string &endpoint) const; + const std::string &computeScreenEndpoint( + not_null participant) const; + const std::string &computeCameraEndpoint( + not_null participant) const; + void setRowVideoEndpoint( + not_null row, + const std::string &endpoint); void appendInvitedUsers(); void scheduleRaisedHandStatusRemove(); @@ -408,6 +414,7 @@ private: base::Timer _raisedHandStatusRemoveTimer; base::flat_map> _soundingRowBySsrc; + base::flat_map> _videoEndpoints; Ui::Animations::Basic _soundingAnimation; crl::time _soundingAnimationHideLastTime = 0; @@ -1036,6 +1043,7 @@ const std::string &Row::videoTrackEndpoint() const { void Row::clearVideoTrack() { _videoTrackLifetime.destroy(); + _videoTrackEndpoint = std::string(); _videoTrackShown = nullptr; _videoTrack = nullptr; _delegate->rowUpdateRow(this); @@ -1139,6 +1147,25 @@ MembersController::~MembersController() { base::take(_menu); } +void MembersController::setRowVideoEndpoint( + not_null row, + const std::string &endpoint) { + const auto was = row->videoTrackEndpoint(); + if (was != endpoint) { + if (!was.empty()) { + _videoEndpoints.remove(was); + } + if (!endpoint.empty()) { + _videoEndpoints.emplace(endpoint, row); + } + } + if (endpoint.empty()) { + row->clearVideoTrack(); + } else { + _call->addVideoOutput(endpoint, row->createVideoTrack(endpoint)); + } +} + void MembersController::setupListChangeViewers() { _call->real( ) | rpl::start_with_next([=](not_null real) { @@ -1164,12 +1191,15 @@ void MembersController::setupListChangeViewers() { ) | rpl::filter([=](const std::string &largeEndpoint) { return (_largeEndpoint != largeEndpoint); }) | rpl::start_with_next([=](const std::string &largeEndpoint) { - if (const auto participant = findParticipant(_largeEndpoint)) { - if (participant->cameraEndpoint() == _largeEndpoint) { + if (_call->streamsVideo(_largeEndpoint)) { + if (const auto participant = findParticipant(_largeEndpoint)) { if (const auto row = findRow(participant->peer)) { - _call->addVideoOutput( - _largeEndpoint, - row->createVideoTrack(_largeEndpoint)); + const auto current = row->videoTrackEndpoint(); + if (current.empty() + || (computeScreenEndpoint(participant) == _largeEndpoint + && computeCameraEndpoint(participant) == current)) { + setRowVideoEndpoint(row, _largeEndpoint); + } } } } @@ -1177,7 +1207,17 @@ void MembersController::setupListChangeViewers() { if (const auto participant = findParticipant(_largeEndpoint)) { if (const auto row = findRow(participant->peer)) { if (row->videoTrackEndpoint() == _largeEndpoint) { - row->clearVideoTrack(); + const auto &camera = computeCameraEndpoint(participant); + const auto &screen = computeScreenEndpoint(participant); + if (_largeEndpoint == camera + && _call->streamsVideo(screen)) { + setRowVideoEndpoint(row, screen); + } else if (_largeEndpoint == screen + && _call->streamsVideo(camera)) { + setRowVideoEndpoint(row, camera); + } else { + setRowVideoEndpoint(row, std::string()); + } } } } @@ -1186,20 +1226,47 @@ void MembersController::setupListChangeViewers() { _call->streamsVideoUpdates( ) | rpl::start_with_next([=](StreamsVideoUpdate update) { Assert(update.endpoint != _largeEndpoint); - if (const auto participant = findParticipant(update.endpoint)) { - if (update.streams) { - if (participant->cameraEndpoint() == update.endpoint - || !_call->streamsVideo(participant->cameraEndpoint())) { - if (const auto row = findRow(participant->peer)) { - _call->addVideoOutput( - update.endpoint, - row->createVideoTrack(update.endpoint)); + if (update.streams) { + if (const auto participant = findParticipant(update.endpoint)) { + if (const auto row = findRow(participant->peer)) { + const auto &camera = computeCameraEndpoint(participant); + const auto &screen = computeScreenEndpoint(participant); + if (update.endpoint == camera + && (!_call->streamsVideo(screen) + || _largeEndpoint == screen)) { + setRowVideoEndpoint(row, camera); + } else if (update.endpoint == screen + && (_largeEndpoint != screen)) { + setRowVideoEndpoint(row, screen); } } - } else { - if (const auto row = findRow(participant->peer)) { - if (row->videoTrackEndpoint() == update.endpoint) { - row->clearVideoTrack(); + } + } else { + const auto i = _videoEndpoints.find(update.endpoint); + if (i != end(_videoEndpoints)) { + const auto row = i->second; + const auto real = _call->lookupReal(); + Assert(real != nullptr); + const auto &participants = real->participants(); + const auto j = ranges::find( + participants, + row->peer(), + &Data::GroupCallParticipant::peer); + if (j == end(participants)) { + setRowVideoEndpoint(row, std::string()); + } else { + const auto &camera = computeCameraEndpoint(&*j); + const auto &screen = computeScreenEndpoint(&*j); + if (update.endpoint == camera + && (_largeEndpoint != screen) + && _call->streamsVideo(screen)) { + setRowVideoEndpoint(row, screen); + } else if (update.endpoint == screen + && (_largeEndpoint != camera) + && _call->streamsVideo(camera)) { + setRowVideoEndpoint(row, camera); + } else { + setRowVideoEndpoint(row, std::string()); } } } @@ -1225,7 +1292,7 @@ void MembersController::setupListChangeViewers() { void MembersController::subscribeToChanges(not_null real) { _fullCount = real->fullCountValue(); - real->participantsSliceAdded( + real->participantsReloaded( ) | rpl::start_with_next([=] { prepareRows(real); }, _lifetime); @@ -1531,12 +1598,33 @@ const Data::GroupCallParticipant *MembersController::findParticipant( return nullptr; } const auto real = _call->lookupReal(); - return real ? real->participantByEndpoint(endpoint) : nullptr; + if (!real) { + return nullptr; + } else if (endpoint == _call->screenSharingEndpoint() + || endpoint == _call->cameraSharingEndpoint()) { + const auto &participants = real->participants(); + const auto i = ranges::find( + participants, + _call->joinAs(), + &Data::GroupCallParticipant::peer); + return (i != end(participants)) ? &*i : nullptr; + } else { + return real->participantByEndpoint(endpoint); + } } -Row *MembersController::findRow(const std::string &endpoint) const { - const auto participant = findParticipant(endpoint); - return participant ? findRow(participant->peer) : nullptr; +const std::string &MembersController::computeScreenEndpoint( + not_null participant) const { + return (participant->peer == _call->joinAs()) + ? _call->screenSharingEndpoint() + : participant->screenEndpoint(); +} + +const std::string &MembersController::computeCameraEndpoint( + not_null participant) const { + return (participant->peer == _call->joinAs()) + ? _call->cameraSharingEndpoint() + : participant->cameraEndpoint(); } Main::Session &MembersController::session() const { @@ -1894,8 +1982,8 @@ base::unique_qptr MembersController::createRowContextMenu( participantPeer, &Data::GroupCallParticipant::peer); if (i != end(participants)) { - const auto camera = i->cameraEndpoint(); - const auto screen = i->screenEndpoint(); + const auto &camera = computeCameraEndpoint(&*i); + const auto &screen = computeScreenEndpoint(&*i); const auto streamsScreen = _call->streamsVideo(screen); if (streamsScreen || _call->streamsVideo(camera)) { const auto callback = [=] { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 9dc9ed036..3a4cdb5b0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -722,7 +722,7 @@ void Panel::refreshLeftButton() { &st::groupCallVideoActiveSmall); _video->show(); _video->setClickedCallback([=] { - _call->toggleVideo(!_call->isCameraSharing()); + _call->toggleVideo(!_call->isSharingCamera()); }); _video->setText(tr::lng_group_call_video()); _video->setColorOverrides(_mute->colorOverrides()); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index 1356866d5..aec5a269b 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -113,42 +113,50 @@ void GroupCall::requestParticipants() { : _nextOffset), MTP_int(kRequestPerPage) )).done([=](const MTPphone_GroupParticipants &result) { - _participantsRequestId = 0; - processSavedFullCall(); result.match([&](const MTPDphone_groupParticipants &data) { + _participantsRequestId = 0; + const auto reloaded = processSavedFullCall(); _nextOffset = qs(data.vnext_offset()); _peer->owner().processUsers(data.vusers()); _peer->owner().processChats(data.vchats()); applyParticipantsSlice( data.vparticipants().v, - ApplySliceSource::SliceLoaded); + (reloaded + ? ApplySliceSource::FullReloaded + : ApplySliceSource::SliceLoaded)); setServerParticipantsCount(data.vcount().v); if (data.vparticipants().v.isEmpty()) { _allParticipantsLoaded = true; } finishParticipantsSliceRequest(); + if (reloaded) { + _participantsReloaded.fire({}); + } }); }).fail([=](const MTP::Error &error) { _participantsRequestId = 0; - processSavedFullCall(); + const auto reloaded = processSavedFullCall(); setServerParticipantsCount(_participants.size()); _allParticipantsLoaded = true; finishParticipantsSliceRequest(); + if (reloaded) { + _participantsReloaded.fire({}); + } }).send(); } -void GroupCall::processSavedFullCall() { +bool GroupCall::processSavedFullCall() { if (!_savedFull) { - return; + return false; } _reloadRequestId = 0; processFullCallFields(*base::take(_savedFull)); + return true; } void GroupCall::finishParticipantsSliceRequest() { computeParticipantsCount(); processQueuedUpdates(); - _participantsSliceAdded.fire({}); } void GroupCall::setServerParticipantsCount(int count) { @@ -237,8 +245,8 @@ const GroupCallParticipant *GroupCall::participantByEndpoint( return nullptr; } -rpl::producer<> GroupCall::participantsSliceAdded() { - return _participantsSliceAdded.events(); +rpl::producer<> GroupCall::participantsReloaded() { + return _participantsReloaded.events(); } auto GroupCall::participantUpdated() const @@ -348,7 +356,7 @@ void GroupCall::processFullCallFields(const MTPphone_GroupCall &call) { applyParticipantsSlice( participants, - ApplySliceSource::SliceLoaded); + ApplySliceSource::FullReloaded); _nextOffset = nextOffset; applyCallFields(data); @@ -362,6 +370,7 @@ void GroupCall::processFullCall(const MTPphone_GroupCall &call) { processFullCallUsersChats(call); processFullCallFields(call); finishParticipantsSliceRequest(); + _participantsReloaded.fire({}); } void GroupCall::applyCallFields(const MTPDgroupCall &data) { @@ -540,7 +549,7 @@ void GroupCall::applyParticipantsSlice( eraseVideoSsrcs(*i); _speakingByActiveFinishes.remove(participantPeer); _participants.erase(i); - if (sliceSource != ApplySliceSource::SliceLoaded) { + if (sliceSource != ApplySliceSource::FullReloaded) { _participantUpdates.fire(std::move(update)); } } @@ -578,24 +587,18 @@ void GroupCall::applyParticipantsSlice( && (!was || was->onlyMinLoaded); const auto raisedHandRating = data.vraise_hand_rating().value_or_empty(); - const auto hasVideoParamsInformation = true/*(sliceSource - != ApplySliceSource::UpdateConstructed)*/; const auto value = Participant{ .peer = participantPeer, - .videoParams = (hasVideoParamsInformation - ? Calls::ParseVideoParams( - (data.vvideo() - ? data.vvideo()->c_dataJSON().vdata().v - : QByteArray()), - (data.vpresentation() - ? data.vpresentation()->c_dataJSON().vdata().v - : QByteArray()), - (i != end(_participants) - ? i->videoParams - : nullptr)) - : (i != end(_participants)) - ? i->videoParams - : nullptr), + .videoParams = Calls::ParseVideoParams( + (data.vvideo() + ? data.vvideo()->c_dataJSON().vdata().v + : QByteArray()), + (data.vpresentation() + ? data.vpresentation()->c_dataJSON().vdata().v + : QByteArray()), + (i != end(_participants) + ? i->videoParams + : nullptr)), .date = data.vdate().v, .lastActive = lastActive, .raisedHandRating = raisedHandRating, @@ -633,7 +636,7 @@ void GroupCall::applyParticipantsSlice( if (data.is_just_joined()) { ++_serverParticipantsCount; } - if (sliceSource != ApplySliceSource::SliceLoaded) { + if (sliceSource != ApplySliceSource::FullReloaded) { _participantUpdates.fire({ .was = was, .now = value, diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 4015f239f..e0eba0607 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -111,7 +111,7 @@ public: [[nodiscard]] const Participant *participantByEndpoint( const std::string &endpoint) const; - [[nodiscard]] rpl::producer<> participantsSliceAdded(); + [[nodiscard]] rpl::producer<> participantsReloaded(); [[nodiscard]] rpl::producer participantUpdated() const; void enqueueUpdate(const MTPUpdate &update); @@ -146,6 +146,7 @@ public: private: enum class ApplySliceSource { + FullReloaded, SliceLoaded, UnknownLoaded, UpdateReceived, @@ -175,7 +176,7 @@ private: void processFullCallFields(const MTPphone_GroupCall &call); [[nodiscard]] bool requestParticipantsAfterReload( const MTPphone_GroupCall &call) const; - void processSavedFullCall(); + [[nodiscard]] bool processSavedFullCall(); void finishParticipantsSliceRequest(); void emplaceVideoSsrcs(const Participant &participant); @@ -218,7 +219,7 @@ private: mtpRequestId _unknownParticipantPeersRequestId = 0; rpl::event_stream _participantUpdates; - rpl::event_stream<> _participantsSliceAdded; + rpl::event_stream<> _participantsReloaded; bool _joinMuted = false; bool _canChangeJoinMuted = true; diff --git a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp index 1f7e5d5fe..31de05ef9 100644 --- a/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp +++ b/Telegram/SourceFiles/history/view/history_view_group_call_tracker.cpp @@ -309,7 +309,7 @@ rpl::producer GroupCallTracker::ContentByCall( } }, lifetime); - call->participantsSliceAdded( + call->participantsReloaded( ) | rpl::filter([=] { return RegenerateUserpics(state, call, userpicSize); }) | rpl::start_with_next(pushNext, lifetime); From 0dcc7a05f78b2de9d69af1fe2508f4ceff71dc19 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 4 May 2021 17:55:25 +0400 Subject: [PATCH 096/477] Fix crash in pinned video switching. --- Telegram/SourceFiles/calls/group/calls_group_call.cpp | 4 ++-- Telegram/ThirdParty/tgcalls | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 95e2af545..773ccc7bf 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1815,9 +1815,9 @@ void GroupCall::ensureControllerCreated() { _videoEndpointLarge.changes( ) | rpl::start_with_next([=](const std::string &endpoint) { _instance->setFullSizeVideoEndpointId(endpoint); + _videoLargeTrack = nullptr; + _videoLargeTrackWrap = nullptr; if (endpoint.empty()) { - _videoLargeTrack = nullptr; - _videoLargeTrackWrap = nullptr; return; } if (!_videoLargeTrackWrap) { diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls index 697ef2ed6..6ee9e983b 160000 --- a/Telegram/ThirdParty/tgcalls +++ b/Telegram/ThirdParty/tgcalls @@ -1 +1 @@ -Subproject commit 697ef2ed67cfcad81b0e61caf0945a057a847327 +Subproject commit 6ee9e983b8eebceb2603633c12d4cf2b973be046 From 00ce302b381e031da2a9f7fe208ab35c44db736d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 6 May 2021 19:57:40 +0400 Subject: [PATCH 097/477] Improve narrow participants column design. --- Telegram/Resources/langs/lang.strings | 6 +- Telegram/SourceFiles/calls/calls.style | 18 +- .../calls/group/calls_group_call.cpp | 49 +++-- .../calls/group/calls_group_call.h | 13 +- .../calls/group/calls_group_members.cpp | 168 +++++++++++++----- .../calls/group/calls_group_members.h | 2 +- .../calls/group/calls_group_panel.cpp | 48 +++-- .../SourceFiles/settings/settings_common.cpp | 68 ++++--- .../SourceFiles/settings/settings_common.h | 9 +- Telegram/ThirdParty/tgcalls | 2 +- Telegram/lib_rpl | 2 +- Telegram/lib_ui | 2 +- 12 files changed, 271 insertions(+), 116 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4cf23232f..581c36a0d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2069,8 +2069,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_context_remove_hand" = "Cancel request to speak"; "lng_group_call_context_mute_for_me" = "Mute for me"; "lng_group_call_context_unmute_for_me" = "Unmute for me"; -"lng_group_call_context_pin_video" = "Pin video"; -"lng_group_call_context_unpin_video" = "Unpin video"; +"lng_group_call_context_pin_camera" = "Pin video"; +"lng_group_call_context_unpin_camera" = "Unpin video"; +"lng_group_call_context_pin_screen" = "Pin screencast"; +"lng_group_call_context_unpin_screen" = "Unpin screencast"; "lng_group_call_context_remove" = "Remove"; "lng_group_call_remove_channel" = "Remove {channel} from the voice chat?"; "lng_group_call_duration_days#one" = "{count} day"; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index afc113e1a..0344576f4 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -811,9 +811,6 @@ groupCallHangupSmall: CallButton(groupCallHangup) { groupCallVideoSmallInner: IconButton(groupCallSettingsInner) { icon: icon {{ "calls/call_camera_muted", groupCallIconFg }}; iconPosition: point(-1px, 16px); - ripple: RippleAnimation(defaultRippleAnimation) { - color: groupCallLeaveBgRipple; - } height: 76px; } groupCallVideoSmall: CallButton(groupCallShareSmall) { @@ -1136,6 +1133,19 @@ desktopCaptureSubmit: RoundButton(desktopCaptureCancel) { groupCallNarrowSkip: 9px; groupCallNarrowRowSkip: 8px; -groupCallNarrowSize: size(90px, 90px); +groupCallNarrowSize: size(144px, 90px); groupCallWideModeWidthMin: 520px; groupCallWideModeSize: size(720px, 480px); +groupCallNarrowAddMemberHeight: 32px; + +groupCallNarrowAddMember: RoundButton(defaultActiveButton) { + textFg: groupCallMemberNotJoinedStatus; + textFgOver: groupCallMemberNotJoinedStatus; + textBg: groupCallMembersBg; + textBgOver: groupCallMembersBgOver; + + height: 32px; + radius: roundRadiusLarge; + + ripple: groupCallRipple; +} diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 773ccc7bf..21bf245e8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -388,6 +388,10 @@ GroupCall::GroupCall( , _joinHash(info.joinHash) , _id(inputCall.c_inputGroupCall().vid().v) , _scheduleDate(info.scheduleDate) +, _cameraOutgoing(std::make_unique( + Webrtc::VideoState::Inactive)) +, _screenOutgoing(std::make_unique( + Webrtc::VideoState::Inactive)) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) , _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) @@ -460,8 +464,15 @@ GroupCall::~GroupCall() { } bool GroupCall::isSharingScreen() const { - return _screenOutgoing - && (_screenOutgoing->state() == Webrtc::VideoState::Active); + return (_screenOutgoing->state() == Webrtc::VideoState::Active); +} + +rpl::producer GroupCall::isSharingScreenValue() const { + using namespace rpl::mappers; + return _screenOutgoing->stateValue( + ) | rpl::map( + _1 == Webrtc::VideoState::Active + ) | rpl::distinct_until_changed(); } const std::string &GroupCall::screenSharingEndpoint() const { @@ -469,8 +480,15 @@ const std::string &GroupCall::screenSharingEndpoint() const { } bool GroupCall::isSharingCamera() const { - return _cameraOutgoing - && (_cameraOutgoing->state() == Webrtc::VideoState::Active); + return (_cameraOutgoing->state() == Webrtc::VideoState::Active); +} + +rpl::producer GroupCall::isSharingCameraValue() const { + using namespace rpl::mappers; + return _cameraOutgoing->stateValue( + ) | rpl::map( + _1 == Webrtc::VideoState::Active + ) | rpl::distinct_until_changed(); } const std::string &GroupCall::cameraSharingEndpoint() const { @@ -611,7 +629,7 @@ void GroupCall::subscribeToReal(not_null real) { const auto wasSounding = data.was && data.was->sounding; if (nowSpeaking == wasSpeaking && nowSounding == wasSounding) { return; - } else if (!_videoEndpointPinned.empty()) { + } else if (!_videoEndpointPinned.current().empty()) { return; } if (nowScreenEndpoint != newLarge @@ -876,7 +894,7 @@ void GroupCall::setMyEndpointType( } const auto nowLarge = activeVideoEndpointType( _videoEndpointLarge.current()); - if (_videoEndpointPinned.empty() + if (_videoEndpointPinned.current().empty() && ((type == EndpointType::Screen && nowLarge != EndpointType::Screen) || (type == EndpointType::Camera @@ -1604,14 +1622,10 @@ void GroupCall::setupMediaDevices() { void GroupCall::ensureOutgoingVideo() { Expects(_id != 0); - if (_cameraOutgoing) { + if (_videoInited) { return; } - - _cameraOutgoing = std::make_unique( - Webrtc::VideoState::Inactive); - _screenOutgoing = std::make_unique( - Webrtc::VideoState::Inactive); + _videoInited = true; //static const auto hasDevices = [] { // return !Webrtc::GetVideoInputList().empty(); @@ -2057,7 +2071,7 @@ void GroupCall::setIncomingVideoEndpoints( feedOne(cameraSharingEndpoint()); feedOne(screenSharingEndpoint()); if (!newLarge.empty() && !newLargeFound) { - newLarge = _videoEndpointPinned = std::string(); + _videoEndpointPinned = newLarge = std::string(); } if (newLarge.empty()) { _videoEndpointLarge = chooseLargeVideoEndpoint(); @@ -2105,7 +2119,7 @@ void GroupCall::fillActiveVideoEndpoints() { feedOne(cameraSharingEndpoint(), EndpointType::Camera); feedOne(screenSharingEndpoint(), EndpointType::Screen); if (!newLarge.empty() && !newLargeFound) { - newLarge = _videoEndpointPinned = std::string(); + _videoEndpointPinned = newLarge = std::string(); } if (newLarge.empty()) { _videoEndpointLarge = chooseLargeVideoEndpoint(); @@ -2453,8 +2467,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { MTP_bool(muted() != MuteState::Active), MTP_int(100000), // volume MTP_bool(muted() == MuteState::RaisedHand), - MTP_bool(!_cameraOutgoing - || _cameraOutgoing->state() != Webrtc::VideoState::Active) + MTP_bool(_cameraOutgoing->state() != Webrtc::VideoState::Active) )).done([=](const MTPUpdates &result) { _updateMuteRequestId = 0; _peer->session().api().applyUpdates(result); @@ -2472,7 +2485,9 @@ void GroupCall::pinVideoEndpoint(const std::string &endpoint) { if (endpoint.empty()) { _videoEndpointPinned = endpoint; } else if (streamsVideo(endpoint)) { - _videoEndpointLarge = _videoEndpointPinned = endpoint; + _videoEndpointPinned = std::string(); + _videoEndpointLarge = endpoint; + _videoEndpointPinned = endpoint; } } diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 7c5e916e0..bbdfc07e1 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -235,10 +235,13 @@ public: && activeVideoEndpointType(endpoint) != EndpointType::None; } [[nodiscard]] const std::string &videoEndpointPinned() const { - return _videoEndpointPinned; + return _videoEndpointPinned.current(); + } + [[nodiscard]] rpl::producer videoEndpointPinnedValue() const { + return _videoEndpointPinned.value(); } void pinVideoEndpoint(const std::string &endpoint); - [[nodiscard]] std::string videoEndpointLarge() const { + [[nodiscard]] const std::string &videoEndpointLarge() const { return _videoEndpointLarge.current(); } [[nodiscard]] auto videoEndpointLargeValue() const @@ -266,8 +269,10 @@ public: void setCurrentAudioDevice(bool input, const QString &deviceId); void setCurrentVideoDevice(const QString &deviceId); [[nodiscard]] bool isSharingScreen() const; + [[nodiscard]] rpl::producer isSharingScreenValue() const; [[nodiscard]] const std::string &screenSharingEndpoint() const; [[nodiscard]] bool isSharingCamera() const; + [[nodiscard]] rpl::producer isSharingCameraValue() const; [[nodiscard]] const std::string &cameraSharingEndpoint() const; [[nodiscard]] QString screenSharingDeviceId() const; void toggleVideo(bool active); @@ -462,12 +467,14 @@ private: std::unique_ptr _screenOutgoing; QString _screenDeviceId; + bool _videoInited = false; + rpl::event_stream _levelUpdates; rpl::event_stream _streamsVideoUpdated; base::flat_set _incomingVideoEndpoints; base::flat_map _activeVideoEndpoints; rpl::variable _videoEndpointLarge; - std::string _videoEndpointPinned; + rpl::variable _videoEndpointPinned; std::unique_ptr _videoLargeTrackWrap; rpl::variable _videoLargeTrack; base::flat_map _lastSpoke; diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index 2a8f7487a..ec6796224 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -185,7 +185,8 @@ public: int x, int y, int outerWidth, - int size, + int sizew, + int sizeh, PanelMode mode, bool selected = false); @@ -256,19 +257,32 @@ private: void ensureUserpicCache( std::shared_ptr &view, int size); - bool paintVideo(Painter &p, int x, int y, int size, PanelMode mode); + bool paintVideo( + Painter &p, + int x, + int y, + int sizew, + int sizeh, + PanelMode mode); [[nodiscard]] static std::tuple UserpicInWideMode( int x, int y, - int size); - void paintBlobs(Painter &p, int x, int y, int size, PanelMode mode); + int sizew, + int sizeh); + void paintBlobs( + Painter &p, + int x, + int y, + int sizew, + int sizeh, PanelMode mode); void paintScaledUserpic( Painter &p, std::shared_ptr &userpic, int x, int y, int outerWidth, - int size, + int sizew, + int sizeh, PanelMode mode); const not_null _delegate; @@ -694,7 +708,13 @@ void Row::ensureUserpicCache( } } -bool Row::paintVideo(Painter &p, int x, int y, int size, PanelMode mode) { +bool Row::paintVideo( + Painter &p, + int x, + int y, + int sizew, + int sizeh, + PanelMode mode) { if (!_videoTrackShown) { return false; } @@ -706,12 +726,14 @@ bool Row::paintVideo(Painter &p, int x, int y, int size, PanelMode mode) { || _videoTrackShown->state() != Webrtc::VideoState::Active) { return false; } - const auto resize = (videoSize.width() > videoSize.height()) - ? QSize(videoSize.width() * size / videoSize.height(), size) - : QSize(size, videoSize.height() * size / videoSize.width()); + const auto videow = videoSize.width(); + const auto videoh = videoSize.height(); + const auto resize = (videow * sizeh > videoh * sizew) + ? QSize(videow * sizeh / videoh, sizeh) + : QSize(sizew, videoh * sizew / videow); const auto request = Webrtc::FrameRequest{ .resize = resize * cIntRetinaFactor(), - .outer = QSize(size, size) * cIntRetinaFactor(), + .outer = QSize(sizew, sizeh) * cIntRetinaFactor(), }; const auto frame = _videoTrackShown->frame(request); auto copy = frame; // #TODO calls optimize. @@ -727,18 +749,30 @@ bool Row::paintVideo(Painter &p, int x, int y, int size, PanelMode mode) { return true; } -std::tuple Row::UserpicInWideMode(int x, int y, int size) { +std::tuple Row::UserpicInWideMode( + int x, + int y, + int sizew, + int sizeh) { const auto useSize = st::groupCallMembersList.item.photoSize; - const auto skip = (size - useSize) / 2; - return { x + skip, y + skip, useSize }; + const auto skipx = (sizew - useSize) / 2; + const auto skipy = (sizeh - useSize) / 2; + return { x + skipx, y + skipy, useSize }; } -void Row::paintBlobs(Painter &p, int x, int y, int size, PanelMode mode) { +void Row::paintBlobs( + Painter &p, + int x, + int y, + int sizew, + int sizeh, + PanelMode mode) { if (!_blobsAnimation) { return; } + auto size = sizew; if (mode == PanelMode::Wide) { - std::tie(x, y, size) = UserpicInWideMode(x, y, size); + std::tie(x, y, size) = UserpicInWideMode(x, y, sizew, sizeh); } const auto mutedByMe = (_state == State::MutedByMe); const auto shift = QPointF(x + size / 2., y + size / 2.); @@ -761,10 +795,12 @@ void Row::paintScaledUserpic( int x, int y, int outerWidth, - int size, + int sizew, + int sizeh, PanelMode mode) { + auto size = sizew; if (mode == PanelMode::Wide) { - std::tie(x, y, size) = UserpicInWideMode(x, y, size); + std::tie(x, y, size) = UserpicInWideMode(x, y, sizew, sizeh); } if (!_blobsAnimation) { peer()->paintUserpicLeft(p, userpic, x, y, outerWidth, size); @@ -800,7 +836,8 @@ void Row::paintScaledUserpic( auto Row::generatePaintUserpicCallback() -> PaintRoundImageCallback { return [=](Painter &p, int x, int y, int outerWidth, int size) { - paintComplexUserpic(p, x, y, outerWidth, size, PanelMode::Default); + const auto outer = outerWidth; + paintComplexUserpic(p, x, y, outer, size, size, PanelMode::Default); }; } @@ -809,18 +846,20 @@ void Row::paintComplexUserpic( int x, int y, int outerWidth, - int size, + int sizew, + int sizeh, PanelMode mode, bool selected) { if (mode == PanelMode::Wide) { - if (paintVideo(p, x, y, size, mode)) { + if (paintVideo(p, x, y, sizew, sizeh, mode)) { return; } _delegate->rowPaintWideBackground(p, selected); paintRipple(p, x, y, outerWidth); } - paintBlobs(p, x, y, size, mode); - if (mode == PanelMode::Default && paintVideo(p, x, y, size, mode)) { + paintBlobs(p, x, y, sizew, sizeh, mode); + if (mode == PanelMode::Default + && paintVideo(p, x, y, sizew, sizeh, mode)) { return; } paintScaledUserpic( @@ -829,7 +868,8 @@ void Row::paintComplexUserpic( x, y, outerWidth, - size, + sizew, + sizeh, mode); } @@ -1826,7 +1866,7 @@ void MembersController::rowPaintIcon( void MembersController::rowPaintWideBackground(Painter &p, bool selected) { (selected ? _wideRoundRectSelected : _wideRoundRect).paint( p, - { QPoint(), st::groupCallNarrowSize }); + { QPoint(st::groupCallNarrowSkip, 0), st::groupCallNarrowSize }); } int MembersController::customRowHeight() { @@ -1840,12 +1880,14 @@ void MembersController::customRowPaint( bool selected) { const auto real = static_cast(row.get()); const auto width = st::groupCallNarrowSize.width(); + const auto height = st::groupCallNarrowSize.height(); real->paintComplexUserpic( p, - 0, + st::groupCallNarrowSkip, 0, width, width, + height, PanelMode::Wide, selected); } @@ -1854,7 +1896,9 @@ bool MembersController::customRowSelectionPoint( not_null row, int x, int y) { - return y < st::groupCallNarrowSize.height(); + return x >= st::groupCallNarrowSkip + && x < st::groupCallNarrowSkip + st::groupCallNarrowSize.width() + && y < st::groupCallNarrowSize.height(); } Fn MembersController::customRowRippleMaskGenerator() { @@ -1973,7 +2017,7 @@ base::unique_qptr MembersController::createRowContextMenu( const auto participant = real->participantByEndpoint(pinnedEndpoint); if (participant && participant->peer == participantPeer) { result->addAction( - tr::lng_group_call_context_unpin_video(tr::now), + tr::lng_group_call_context_unpin_camera(tr::now), [=] { _call->pinVideoEndpoint(std::string()); }); } else { const auto &participants = real->participants(); @@ -1992,7 +2036,7 @@ base::unique_qptr MembersController::createRowContextMenu( : camera); }; result->addAction( - tr::lng_group_call_context_pin_video(tr::now), + tr::lng_group_call_context_pin_camera(tr::now), callback); } } @@ -2292,27 +2336,61 @@ void Members::setupAddMember(not_null call) { }); } - _canAddMembers.value( - ) | rpl::start_with_next([=](bool can) { + rpl::combine( + _canAddMembers.value(), + _mode.value() + ) | rpl::start_with_next([=](bool can, PanelMode mode) { + const auto old = _addMemberButton.current(); + delete old; if (!can) { - delete _addMemberButton.current(); - _addMemberButton = nullptr; - updateControlsGeometry(); - return; - } else if (_addMemberButton.current()) { + if (old) { + _addMemberButton = nullptr; + updateControlsGeometry(); + } return; } - auto addMember = Settings::CreateButton( - this, - tr::lng_group_call_invite(), - st::groupCallAddMember, - &st::groupCallAddMemberIcon, - st::groupCallAddMemberIconLeft); + auto addMember = (Ui::AbstractButton*)nullptr; + auto wrap = [&]() -> object_ptr { + if (mode == PanelMode::Default) { + auto result = Settings::CreateButton( + this, + tr::lng_group_call_invite(), + st::groupCallAddMember, + &st::groupCallAddMemberIcon, + st::groupCallAddMemberIconLeft, + &st::groupCallMemberInactiveIcon); + addMember = result.data(); + return result; + } + auto result = object_ptr(_layout.get()); + const auto skip = st::groupCallNarrowSkip; + const auto fullwidth = st::groupCallNarrowSize.width() + + 2 * skip; + const auto fullheight = st::groupCallNarrowAddMember.height + + st::groupCallNarrowRowSkip; + result->resize(fullwidth, fullheight); + const auto button = Ui::CreateChild( + result.data(), + rpl::single(QString()), + st::groupCallNarrowAddMember); + button->move(skip, 0); + const auto width = fullwidth - 2 * skip; + button->setFullWidth(width); + Settings::AddButtonIcon( + button, + &st::groupCallAddMemberIcon, + (width - st::groupCallAddMemberIcon.width()) / 2, + &st::groupCallMemberInactiveIcon); + addMember = button; + return result; + }(); addMember->show(); - addMember->addClickHandler([=] { // TODO throttle(ripple duration) - _addMemberRequests.fire({}); - }); - _addMemberButton = _layout->insert(1, std::move(addMember)); + addMember->clicks( + ) | rpl::to_empty | rpl::start_to_stream( + _addMemberRequests, + addMember->lifetime()); + _addMemberButton = wrap.data(); + _layout->insert(1, std::move(wrap)); }, lifetime()); } diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 3383536cc..5c43d8cf8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -87,7 +87,7 @@ private: std::unique_ptr _listController; not_null _layout; const not_null _pinnedVideo; - rpl::variable _addMemberButton = nullptr; + rpl::variable _addMemberButton = nullptr; ListWidget *_list = nullptr; rpl::event_stream<> _addMemberRequests; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 3a4cdb5b0..4a15bce3e 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -65,6 +65,7 @@ constexpr auto kRecordingAnimationDuration = crl::time(1200); constexpr auto kRecordingOpacity = 0.6; constexpr auto kStartNoConfirmation = TimeId(10); constexpr auto kControlsBackgroundOpacity = 0.8; +constexpr auto kOverrideActiveColorBgAlpha = 236; class InviteController final : public ParticipantsBoxController { public: @@ -713,8 +714,21 @@ void Panel::refreshLeftButton() { } const auto raw = _callShare ? _callShare.data() : _settings.data(); raw->show(); - raw->setColorOverrides(_mute->colorOverrides()); + auto overrides = _mute->colorOverrides(); + raw->setColorOverrides(rpl::duplicate(overrides)); + + auto toggleableOverrides = [&](rpl::producer active) { + return rpl::combine( + std::move(active), + rpl::duplicate(overrides) + ) | rpl::map([](bool active, Ui::CallButtonColors colors) { + if (active && colors.bg) { + colors.bg->setAlpha(kOverrideActiveColorBgAlpha); + } + return colors; + }); + }; if (!_video) { _video.create( widget(), @@ -725,7 +739,12 @@ void Panel::refreshLeftButton() { _call->toggleVideo(!_call->isSharingCamera()); }); _video->setText(tr::lng_group_call_video()); - _video->setColorOverrides(_mute->colorOverrides()); + _video->setColorOverrides( + toggleableOverrides(_call->isSharingCameraValue())); + _call->isSharingCameraValue( + ) | rpl::start_with_next([=](bool sharing) { + _video->setProgress(sharing ? 1. : 0.); + }, _video->lifetime()); } if (!_screenShare) { _screenShare.create(widget(), st::groupCallScreenShareSmall); @@ -736,7 +755,12 @@ void Panel::refreshLeftButton() { #endif // Q_OS_LINUX }); _screenShare->setText(tr::lng_group_call_screen_share()); - _screenShare->setColorOverrides(_mute->colorOverrides()); + _screenShare->setColorOverrides( + toggleableOverrides(_call->isSharingScreenValue())); + _call->isSharingScreenValue( + ) | rpl::start_with_next([=](bool sharing) { + _screenShare->setProgress(sharing ? 1. : 0.); + }, _screenShare->lifetime()); } } @@ -1687,19 +1711,19 @@ void Panel::updateMembersGeometry() { } const auto desiredHeight = _members->desiredHeight(); if (_mode == PanelMode::Wide) { + const auto skip = st::groupCallNarrowSkip; + const auto membersWidth = st::groupCallNarrowSize.width() + 2 * skip; + const auto top = st::groupCallWideVideoTop; _members->setGeometry( - st::groupCallNarrowSkip, 0, - st::groupCallNarrowSize.width(), + top, + membersWidth, std::min(desiredHeight, widget()->height())); - const auto pinnedLeft = st::groupCallNarrowSkip * 2 - + st::groupCallNarrowSize.width(); - const auto pinnedTop = st::groupCallWideVideoTop; _pinnedVideo->setGeometry( - pinnedLeft, - pinnedTop, - widget()->width() - pinnedLeft - st::groupCallNarrowSkip, - widget()->height() - pinnedTop - st::groupCallNarrowSkip); + membersWidth, + top, + widget()->width() - membersWidth - skip, + widget()->height() - top - skip); } else { const auto membersBottom = _videoMode.current() ? (widget()->height() - st::groupCallMembersBottomSkipSmall) diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index f94456ee6..7b9e48c55 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -35,9 +35,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Settings { object_ptr
CreateSection( - Type type, - not_null parent, - not_null controller) { + Type type, + not_null parent, + not_null controller) { switch (type) { case Type::Main: return object_ptr
(parent, controller); @@ -74,8 +74,8 @@ void AddDivider(not_null container) { } void AddDividerText( - not_null container, - rpl::producer text) { + not_null container, + rpl::producer text) { container->add(object_ptr( container, object_ptr( @@ -85,37 +85,49 @@ void AddDividerText( st::settingsDividerLabelPadding)); } +not_null AddButtonIcon( + not_null button, + const style::icon *leftIcon, + int iconLeft, + const style::color *leftIconOver) { + const auto icon = Ui::CreateChild(button.get()); + icon->setAttribute(Qt::WA_TransparentForMouseEvents); + icon->resize(leftIcon->size()); + button->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + icon->moveToLeft( + iconLeft ? iconLeft : st::settingsSectionIconLeft, + (size.height() - icon->height()) / 2, + size.width()); + }, icon->lifetime()); + icon->paintRequest( + ) | rpl::start_with_next([=] { + Painter p(icon); + const auto width = icon->width(); + const auto paintOver = (button->isOver() || button->isDown()) + && !button->isDisabled(); + if (!paintOver) { + leftIcon->paint(p, QPoint(), width); + } else if (leftIconOver) { + leftIcon->paint(p, QPoint(), width, (*leftIconOver)->c); + } else { + leftIcon->paint(p, QPoint(), width, st::menuIconFgOver->c); + } + }, icon->lifetime()); + return icon; +} + object_ptr