diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d72a22e179..4329b12e51 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1540,6 +1540,8 @@ PRIVATE support/support_preload.h support/support_templates.cpp support/support_templates.h + tde2e/tde2e_integration.cpp + tde2e/tde2e_integration.h ui/boxes/edit_invite_link_session.cpp ui/boxes/edit_invite_link_session.h ui/boxes/peer_qr_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 53ac624a08..c908b48a04 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4911,6 +4911,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_join_title" = "Group Call"; "lng_confcall_join_text" = "You are invited to join a Telegram Call."; "lng_confcall_join_button" = "Join Group Call"; +"lng_confcall_create_link" = "Create Call Link"; +"lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 31a4e73cd3..ec65bd15d3 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -303,14 +303,19 @@ void Updates::feedUpdateVector( auto list = updates.v; const auto hasGroupCallParticipantUpdates = ranges::contains( list, - mtpc_updateGroupCallParticipants, - &MTPUpdate::type); + true, + [](const MTPUpdate &update) { + return update.type() == mtpc_updateGroupCallParticipants + || update.type() == mtpc_updateGroupCallChainBlocks; + }); if (hasGroupCallParticipantUpdates) { ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) { - if (entry.type() == mtpc_updateGroupCallParticipants) { + if (entry.type() == mtpc_updateGroupCallChainBlocks) { return 0; - } else { + } else if (entry.type() == mtpc_updateGroupCallParticipants) { return 1; + } else { + return 2; } }); } else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) { @@ -324,7 +329,8 @@ void Updates::feedUpdateVector( if ((policy == SkipUpdatePolicy::SkipMessageIds && type == mtpc_updateMessageID) || (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants - && type != mtpc_updateGroupCallParticipants)) { + && type != mtpc_updateGroupCallParticipants + && type != mtpc_updateGroupCallChainBlocks)) { continue; } feedUpdate(entry); @@ -954,7 +960,8 @@ void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) { data.vupdates(), SkipUpdatePolicy::SkipExceptGroupCallParticipants); }, [&](const MTPDupdateShort &data) { - if (data.vupdate().type() == mtpc_updateGroupCallParticipants) { + if (data.vupdate().type() == mtpc_updateGroupCallParticipants + || data.vupdate().type() == mtpc_updateGroupCallChainBlocks) { feedUpdate(data.vupdate()); } }, [](const auto &) { @@ -2110,6 +2117,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updatePhoneCall: case mtpc_updatePhoneCallSignalingData: case mtpc_updateGroupCallParticipants: + case mtpc_updateGroupCallChainBlocks: case mtpc_updateGroupCallConnection: case mtpc_updateGroupCall: { Core::App().calls().handleUpdate(&session(), update); diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index f9ab9ebe68..a7e7c102ab 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -235,16 +235,18 @@ void Instance::startOrJoinConferenceCall( StartConferenceCallArgs args) { destroyCurrentCall(); + const auto session = &args.call->peer()->session(); auto call = std::make_unique( _delegate.get(), Calls::Group::ConferenceInfo{ - .call = args.call, + .call = std::move(args.call), + .e2e = std::move(args.e2e), .linkSlug = args.linkSlug, .joinMessageId = args.joinMessageId, }); const auto raw = call.get(); - args.call->peer()->session().account().sessionChanges( + session->account().sessionChanges( ) | rpl::start_with_next([=] { destroyGroupCall(raw); }, raw->lifetime()); @@ -547,6 +549,8 @@ void Instance::handleUpdate( handleGroupCallUpdate(session, update); }, [&](const MTPDupdateGroupCallParticipants &data) { handleGroupCallUpdate(session, update); + }, [&](const MTPDupdateGroupCallChainBlocks &data) { + handleGroupCallUpdate(session, update); }, [](const auto &) { Unexpected("Update type in Calls::Instance::handleUpdate."); }); @@ -677,6 +681,12 @@ void Instance::handleGroupCallUpdate( }, [](const MTPDinputGroupCallSlug &) -> CallId { Unexpected("slug in Instance::handleGroupCallUpdate"); }); + }, [](const MTPDupdateGroupCallChainBlocks &data) { + return data.vcall().match([&](const MTPDinputGroupCall &data) { + return data.vid().v; + }, [](const MTPDinputGroupCallSlug &) -> CallId { + Unexpected("slug in Instance::handleGroupCallUpdate"); + }); }, [](const auto &) -> CallId { Unexpected("Type in Instance::handleGroupCallUpdate."); }); diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 724140b60b..2d5df76f2a 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -45,6 +45,10 @@ namespace tgcalls { class VideoCaptureInterface; } // namespace tgcalls +namespace TdE2E { +class Call; +} // namespace TdE2E + namespace Calls { class Call; @@ -66,6 +70,7 @@ struct StartGroupCallArgs { struct StartConferenceCallArgs { std::shared_ptr call; + std::shared_ptr e2e; QString linkSlug; MsgId joinMessageId; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 268b356527..9b00f3da58 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/global_shortcuts.h" #include "base/random.h" #include "tde2e/tde2e_api.h" +#include "tde2e/tde2e_integration.h" #include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_create_adm.h" #include "webrtc/webrtc_environment.h" @@ -53,6 +54,10 @@ constexpr auto kFixManualLargeVideoDuration = 5 * crl::time(1000); constexpr auto kFixSpeakingLargeVideoDuration = 3 * crl::time(1000); constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums. constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 Mediums. +constexpr auto kShortPollChainBlocksTimeout = 5 * crl::time(1000); +constexpr auto kShortPollChainBlocksPerRequest = 50; +constexpr auto kSubChain0 = 0; +constexpr auto kSubChain1 = 1; [[nodiscard]] const Data::GroupCallParticipant *LookupParticipant( not_null peer, @@ -603,7 +608,8 @@ GroupCall::GroupCall( Group::ConferenceInfo conference, const MTPInputGroupCall &inputCall) : _delegate(delegate) -, _conferenceCall(conference.call) +, _conferenceCall(std::move(conference.call)) +, _e2e(std::move(conference.e2e)) , _peer(join.peer) , _history(_peer->owner().history(_peer)) , _api(&_peer->session().mtp()) @@ -641,8 +647,15 @@ GroupCall::GroupCall( , _rtmp(join.rtmp) , _rtmpVolume(Group::kDefaultVolume) { if (_conferenceCall) { - _e2eState = std::make_unique( - TdE2E::CreateCallState()); + if (!_e2e) { + _e2e = std::make_shared( + TdE2E::MakeUserId(_peer->session().user())); + } + for (auto i = 0; i != kSubChainsCount; ++i) { + _subchains[i].timer.setCallback([=] { + checkChainBlocksRequest(i); + }); + } } _muted.value( @@ -1419,10 +1432,7 @@ void GroupCall::rejoin(not_null as) { ? Flag::f_video_stopped : Flag(0)) | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) - | (_e2eState ? Flag::f_public_key : Flag()); - const auto publicKey = _e2eState - ? _e2eState->myKey - : TdE2E::PublicKey(); + | (_e2e ? Flag::f_public_key : Flag()); _api.request(MTPphone_JoinGroupCall( MTP_flags(flags), (_conferenceLinkSlug.isEmpty() @@ -1431,9 +1441,7 @@ void GroupCall::rejoin(not_null as) { MTP_string(_conferenceLinkSlug))), joinAs()->input, MTP_string(_joinHash), - MTP_int256( - MTP_int128(publicKey.a, publicKey.b), - MTP_int128(publicKey.c, publicKey.d)), + (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), MTP_int(_conferenceJoinMessageId.bare), MTP_dataJSON(MTP_bytes(json)) )).done([=]( @@ -1470,6 +1478,8 @@ void GroupCall::rejoin(not_null as) { real->reloadIfStale(); } } + checkChainBlocksRequest(kSubChain0); + checkChainBlocksRequest(kSubChain1); }).fail([=](const MTP::Error &error) { _joinState.finish(); @@ -1490,6 +1500,40 @@ void GroupCall::rejoin(not_null as) { }); } +void GroupCall::checkChainBlocksRequest(int subchain) { + Expects(subchain >= 0 && subchain < kSubChainsCount); + + auto &state = _subchains[subchain]; + if (state.requestId) { + return; + } + const auto now = crl::now(); + const auto left = state.lastUpdate + kShortPollChainBlocksTimeout - now; + if (left > 0) { + if (!state.timer.isActive()) { + state.timer.callOnce(left); + } + return; + } + state.requestId = _api.request(MTPphone_GetGroupCallChainBlocks( + inputCall(), + MTP_int(subchain), + MTP_int(state.height), + MTP_int(kShortPollChainBlocksPerRequest) + )).done([=](const MTPUpdates &result) { + auto &state = _subchains[subchain]; + state.lastUpdate = crl::now(); + state.requestId = 0; + _peer->session().api().applyUpdates(result); + state.timer.callOnce(kShortPollChainBlocksTimeout + 1); + }).fail([=](const MTP::Error &error) { + auto &state = _subchains[subchain]; + state.lastUpdate = crl::now(); + state.requestId = 0; + state.timer.callOnce(kShortPollChainBlocksTimeout + 1); + }).send(); +} + void GroupCall::checkNextJoinAction() { if (_joinState.action != JoinAction::None) { return; @@ -1640,7 +1684,6 @@ void GroupCall::applyMeInCallLocally() { : participant ? participant->raisedHandRating : FindLocalRaisedHandRating(real->participants()); - const auto publicKey = _e2eState ? _e2eState->myKey : TdE2E::PublicKey(); const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) | (lastActive ? Flag::f_active_date : Flag(0)) | (_joinState.ssrc ? Flag(0) : Flag::f_left) @@ -1650,7 +1693,7 @@ void GroupCall::applyMeInCallLocally() { | Flag::f_volume_by_admin // Self volume can only be set by admin. | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) | (raisedHandRating > 0 ? Flag::f_raise_hand_rating : Flag(0)) - | (_e2eState ? Flag::f_public_key : Flag(0)); + | (_e2e ? Flag::f_public_key : Flag(0)); real->applyLocalUpdate( MTP_updateGroupCallParticipants( inputCall(), @@ -1667,9 +1710,9 @@ void GroupCall::applyMeInCallLocally() { MTP_long(raisedHandRating), MTPGroupCallParticipantVideo(), MTPGroupCallParticipantVideo(), - MTP_int256( - MTP_int128(publicKey.a, publicKey.b), - MTP_int128(publicKey.c, publicKey.d)))), + (_e2e + ? TdE2E::PublicKeyToMTP(_e2e->myKey()) + : MTPint256()))), MTP_int(0)).c_updateGroupCallParticipants()); } @@ -2022,6 +2065,8 @@ void GroupCall::handleUpdate(const MTPUpdate &update) { handleUpdate(data); }, [&](const MTPDupdateGroupCallParticipants &data) { handleUpdate(data); + }, [&](const MTPDupdateGroupCallChainBlocks &data) { + handleUpdate(data); }, [](const auto &) { Unexpected("Type in Instance::applyGroupCallUpdateChecked."); }); @@ -2063,6 +2108,33 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) { } } +void GroupCall::handleUpdate(const MTPDupdateGroupCallChainBlocks &data) { + const auto callId = data.vcall().match([]( + const MTPDinputGroupCall &data) { + return data.vid().v; + }, [](const MTPDinputGroupCallSlug &) -> CallId { + Unexpected("inputGroupCallSlug in GroupCall::handleUpdate."); + }); + if (_id != callId || !_e2e) { + return; + } + const auto subchain = data.vsub_chain_id().v; + if (subchain < 0 || subchain >= kSubChainsCount) { + return; + } + auto &entry = _subchains[subchain]; + entry.lastUpdate = crl::now(); + entry.height = data.vnext_offset().v; + entry.timer.callOnce(kShortPollChainBlocksTimeout + 1); + for (const auto &block : data.vblocks().v) { + const auto result = _e2e->apply({ block.v }); + if (result == TdE2E::Call::ApplyResult::BlockSkipped) { + AssertIsDebug(); + return; + } + } +} + void GroupCall::applyQueuedSelfUpdates() { const auto weak = base::make_weak(this); while (weak diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index a3c8d07c12..148b760acb 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -44,7 +44,7 @@ class GroupCall; namespace TdE2E { struct ParticipantState; -struct CallState; +class Call; } // namespace TdE2E namespace Calls { @@ -437,6 +437,7 @@ private: struct SinkPointer; static constexpr uint32 kDisabledSsrc = uint32(-1); + static constexpr int kSubChainsCount = 2; struct LoadingPart { std::shared_ptr task; @@ -475,6 +476,12 @@ private: ssrc = updatedSsrc; } }; + struct SubChainState { + crl::time lastUpdate = 0; + base::Timer timer; + int height = 0; + mtpRequestId requestId = 0; + }; friend inline constexpr bool is_flag_type(SendUpdateType) { return true; @@ -507,6 +514,7 @@ private: void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); + void handleUpdate(const MTPDupdateGroupCallChainBlocks &data); bool tryCreateController(); void destroyController(); bool tryCreateScreencast(); @@ -535,6 +543,7 @@ private: void rejoinPresentation(); void leavePresentation(); void checkNextJoinAction(); + void checkChainBlocksRequest(int subchain); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); void setInstanceConnected(tgcalls::GroupNetworkState networkState); @@ -593,6 +602,8 @@ private: const not_null _delegate; const std::shared_ptr _conferenceCall; + std::shared_ptr _e2e; + not_null _peer; // Can change in legacy group migration. rpl::event_stream _peerStream; not_null _history; // Can change in legacy group migration. @@ -624,8 +635,6 @@ private: int64 _serverTimeMs = 0; crl::time _serverTimeMsGotAt = 0; - std::unique_ptr _e2eState; - QString _rtmpUrl; QString _rtmpKey; @@ -710,6 +719,8 @@ private: bool _reloadedStaleCall = false; int _rtmpVolume = 0; + SubChainState _subchains[kSubChainsCount]; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 32d403ed6f..ea18eadd3a 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -19,6 +19,10 @@ namespace Ui { class GenericBox; } // namespace Ui +namespace TdE2E { +class Call; +} // namespace TdE2E + namespace Calls::Group { constexpr auto kDefaultVolume = 10000; @@ -67,6 +71,7 @@ struct JoinInfo { struct ConferenceInfo { std::shared_ptr call; + std::shared_ptr e2e; QString linkSlug; MsgId joinMessageId; }; diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index d283764b12..506c9019c9 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -19,6 +19,7 @@ struct ParticipantVideoParams; namespace TdE2E { struct ParticipantState; +struct UserId; } // namespace TdE2E namespace Data { diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index f5c8f12001..04ae7340ac 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -12,20 +12,77 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace TdE2E { +namespace { -CallState CreateCallState() { +constexpr auto kPermissionAdd = 1; +constexpr auto kPermissionRemove = 2; + +[[nodiscard]] tde2e_api::Slice Slice(const QByteArray &data) { + return { + data.constData(), + std::string_view::size_type(data.size()) + }; +} + +} // namespace + +Call::Call(UserId myUserId) +: _myUserId(myUserId) { const auto id = tde2e_api::key_generate_temporary_private_key(); Assert(id.is_ok()); - const auto key = tde2e_api::key_to_public_key(id.value()); + _myKeyId = { .v = uint64(id.value()) }; + + const auto key = tde2e_api::key_to_public_key(_myKeyId.v); Assert(key.is_ok()); + Assert(key.value().size() == sizeof(_myKey)); + memcpy(&_myKey, key.value().data(), sizeof(_myKey)); +} - auto result = CallState{ - .myKeyId = PrivateKeyId{ .v = uint64(id.value()) }, +PublicKey Call::myKey() const { + return _myKey; +} + +Block Call::makeZeroBlock() const { + const auto publicKeyView = std::string_view{ + reinterpret_cast(&_myKey), + sizeof(_myKey), }; - Assert(key.value().size() == sizeof(result.myKey)); - memcpy(&result.myKey, key.value().data(), 32); + const auto publicKeyId = tde2e_api::key_from_public_key(publicKeyView); + Assert(publicKeyId.is_ok()); - return result; + const auto myKeyId = std::int64_t(_myKeyId.v); + const auto result = tde2e_api::call_create_zero_block(myKeyId, { + .height = 0, + .participants = { { + .user_id = std::int64_t(_myUserId.v), + .public_key_id = publicKeyId.value(), + .permissions = kPermissionAdd | kPermissionRemove, + } }, + }); + Assert(result.is_ok()); + + return { + .data = QByteArray::fromStdString(result.value()), + }; +} + +void Call::create(const Block &last) { + tde2e_api::call_create(std::int64_t(_myKeyId.v), Slice(last.data)); +} + +Call::ApplyResult Call::apply(const Block &block) { + const auto result = tde2e_api::call_apply_block( + std::int64_t(_id.v), + Slice(block.data)); + + if (!result.is_ok()) { + const auto error = result.error(); + (void)error; + } + + return result.is_ok() + ? ApplyResult::Success + : ApplyResult::BlockSkipped; } } // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 689a5aa86c..36a634576d 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -11,10 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace TdE2E { +struct UserId { + uint64 v = 0; +}; + struct PrivateKeyId { uint64 v = 0; }; +struct CallId { + uint64 v = 0; +}; + struct PublicKey { uint64 a = 0; uint64 b = 0; @@ -23,15 +31,36 @@ struct PublicKey { }; struct ParticipantState { - uint64 id = 0; + UserId id; PublicKey key; }; -struct CallState { - PrivateKeyId myKeyId; - PublicKey myKey; +struct Block { + QByteArray data; }; -[[nodiscard]] CallState CreateCallState(); +class Call final { +public: + explicit Call(UserId myUserId); + + [[nodiscard]] PublicKey myKey() const; + + [[nodiscard]] Block makeZeroBlock() const; + + void create(const Block &last); + + enum class ApplyResult { + Success, + BlockSkipped + }; + [[nodiscard]] ApplyResult apply(const Block &block); + +private: + CallId _id; + UserId _myUserId; + PrivateKeyId _myKeyId; + PublicKey _myKey; + +}; } // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_integration.cpp b/Telegram/SourceFiles/tde2e/tde2e_integration.cpp new file mode 100644 index 0000000000..3f63917d5f --- /dev/null +++ b/Telegram/SourceFiles/tde2e/tde2e_integration.cpp @@ -0,0 +1,27 @@ +/* +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 "tde2e/tde2e_integration.h" + +#include "data/data_user.h" +#include "tde2e/tde2e_api.h" + +namespace TdE2E { + +UserId MakeUserId(not_null user) { + return MakeUserId(peerToUser(user->id)); +} + +UserId MakeUserId(::UserId id) { + return { .v = id.bare }; +} + +MTPint256 PublicKeyToMTP(const PublicKey &key) { + return MTP_int256(MTP_int128(key.a, key.b), MTP_int128(key.c, key.d)); +} + +} // namespace TdE2E diff --git a/Telegram/SourceFiles/tde2e/tde2e_integration.h b/Telegram/SourceFiles/tde2e/tde2e_integration.h new file mode 100644 index 0000000000..4715743859 --- /dev/null +++ b/Telegram/SourceFiles/tde2e/tde2e_integration.h @@ -0,0 +1,20 @@ +/* +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 TdE2E { + +struct UserId; +struct PublicKey; + +[[nodiscard]] UserId MakeUserId(not_null user); +[[nodiscard]] UserId MakeUserId(::UserId id); + +[[nodiscard]] MTPint256 PublicKeyToMTP(const PublicKey &key); + +} // namespace TdE2E diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 0fd4094309..9dede3dcd5 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -14,17 +14,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peer_list_controllers.h" #include "boxes/premium_preview_box.h" #include "calls/calls_box_controller.h" +#include "calls/calls_instance.h" #include "core/application.h" #include "core/click_handler_types.h" #include "data/data_changes.h" #include "data/data_document_media.h" #include "data/data_folder.h" +#include "data/data_group_call.h" #include "data/data_session.h" #include "data/data_stories.h" #include "data/data_user.h" #include "info/info_memento.h" #include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_emoji_status_panel.h" +#include "info/profile/info_profile_icon.h" #include "info/stories/info_stories_widget.h" #include "lang/lang_keys.h" #include "main/main_account.h" @@ -38,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/localstorage.h" #include "storage/storage_account.h" #include "support/support_templates.h" +#include "tde2e/tde2e_api.h" +#include "tde2e/tde2e_integration.h" #include "ui/boxes/confirm_box.h" #include "ui/chat/chat_theme.h" #include "ui/controls/swipe_handler.h" @@ -92,6 +97,66 @@ constexpr auto kPlayStatusLimit = 2; || (now.month() == 1 && now.day() == 1); } +[[nodiscard]] not_null AddCreateCallLinkButton( + not_null container, + not_null controller, + Fn done) { + const auto result = container->add(object_ptr( + container, + tr::lng_confcall_create_link(), + st::inviteViaLinkButton), QMargins()); + Ui::AddSkip(container); + Ui::AddDividerText( + container, + tr::lng_confcall_create_link_description(Ui::Text::WithEntities)); + + const auto icon = Ui::CreateChild( + result, + st::inviteViaLinkIcon, + QPoint()); + result->heightValue( + ) | rpl::start_with_next([=](int height) { + icon->moveToLeft( + st::inviteViaLinkIconPosition.x(), + (height - st::inviteViaLinkIcon.height()) / 2); + }, icon->lifetime()); + + const auto creating = result->lifetime().make_state(); + result->setClickedCallback([=] { + if (*creating) { + return; + } + *creating = true; + auto e2e = std::make_shared( + TdE2E::MakeUserId(controller->session().user())); + const auto session = &controller->session(); + session->api().request(MTPphone_CreateConferenceCall( + TdE2E::PublicKeyToMTP(e2e->myKey()), + MTP_bytes(e2e->makeZeroBlock().data) + )).done(crl::guard(controller, [=](const MTPphone_GroupCall &result) { + result.data().vcall().match([&](const auto &data) { + const auto call = std::make_shared( + session->user(), + data.vid().v, + data.vaccess_hash().v, + TimeId(), // scheduleDate + false); // rtmp + call->processFullCall(result); + Core::App().calls().startOrJoinConferenceCall( + controller->uiShow(), + { .call = call, .e2e = e2e }); + }); + if (const auto onstack = done) { + onstack(); + } + })).fail(crl::guard(controller, [=](const MTP::Error &error) { + controller->uiShow()->showToast(error.type()); + *creating = false; + })).send(); + }); + return result; +} + void ShowCallsBox(not_null window) { struct State { State(not_null window) @@ -129,6 +194,17 @@ void ShowCallsBox(not_null window) { Ui::AddDivider(groupCalls->entity()); Ui::AddSkip(groupCalls->entity()); + const auto button = AddCreateCallLinkButton( + box->verticalLayout(), + window, + crl::guard(box, [=] { box->closeBox(); })); + button->events( + ) | rpl::filter([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=] { + state->callsDelegate.peerListMouseLeftGeometry(); + }, button->lifetime()); + const auto content = box->addRow( object_ptr(box, &state->callsController), {});