PoC conference call creation.

This commit is contained in:
John Preston 2025-03-25 22:38:13 +05:00
parent 5e6c81a98e
commit c80c23e8e8
14 changed files with 363 additions and 38 deletions

View file

@ -1540,6 +1540,8 @@ PRIVATE
support/support_preload.h support/support_preload.h
support/support_templates.cpp support/support_templates.cpp
support/support_templates.h 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.cpp
ui/boxes/edit_invite_link_session.h ui/boxes/edit_invite_link_session.h
ui/boxes/peer_qr_box.cpp ui/boxes/peer_qr_box.cpp

View file

@ -4911,6 +4911,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_confcall_join_title" = "Group Call"; "lng_confcall_join_title" = "Group Call";
"lng_confcall_join_text" = "You are invited to join a Telegram Call."; "lng_confcall_join_text" = "You are invited to join a Telegram Call.";
"lng_confcall_join_button" = "Join Group 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."; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages.";

View file

@ -303,14 +303,19 @@ void Updates::feedUpdateVector(
auto list = updates.v; auto list = updates.v;
const auto hasGroupCallParticipantUpdates = ranges::contains( const auto hasGroupCallParticipantUpdates = ranges::contains(
list, list,
mtpc_updateGroupCallParticipants, true,
&MTPUpdate::type); [](const MTPUpdate &update) {
return update.type() == mtpc_updateGroupCallParticipants
|| update.type() == mtpc_updateGroupCallChainBlocks;
});
if (hasGroupCallParticipantUpdates) { if (hasGroupCallParticipantUpdates) {
ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) { ranges::stable_sort(list, std::less<>(), [](const MTPUpdate &entry) {
if (entry.type() == mtpc_updateGroupCallParticipants) { if (entry.type() == mtpc_updateGroupCallChainBlocks) {
return 0; return 0;
} else { } else if (entry.type() == mtpc_updateGroupCallParticipants) {
return 1; return 1;
} else {
return 2;
} }
}); });
} else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) { } else if (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants) {
@ -324,7 +329,8 @@ void Updates::feedUpdateVector(
if ((policy == SkipUpdatePolicy::SkipMessageIds if ((policy == SkipUpdatePolicy::SkipMessageIds
&& type == mtpc_updateMessageID) && type == mtpc_updateMessageID)
|| (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants || (policy == SkipUpdatePolicy::SkipExceptGroupCallParticipants
&& type != mtpc_updateGroupCallParticipants)) { && type != mtpc_updateGroupCallParticipants
&& type != mtpc_updateGroupCallChainBlocks)) {
continue; continue;
} }
feedUpdate(entry); feedUpdate(entry);
@ -954,7 +960,8 @@ void Updates::applyGroupCallParticipantUpdates(const MTPUpdates &updates) {
data.vupdates(), data.vupdates(),
SkipUpdatePolicy::SkipExceptGroupCallParticipants); SkipUpdatePolicy::SkipExceptGroupCallParticipants);
}, [&](const MTPDupdateShort &data) { }, [&](const MTPDupdateShort &data) {
if (data.vupdate().type() == mtpc_updateGroupCallParticipants) { if (data.vupdate().type() == mtpc_updateGroupCallParticipants
|| data.vupdate().type() == mtpc_updateGroupCallChainBlocks) {
feedUpdate(data.vupdate()); feedUpdate(data.vupdate());
} }
}, [](const auto &) { }, [](const auto &) {
@ -2110,6 +2117,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
case mtpc_updatePhoneCall: case mtpc_updatePhoneCall:
case mtpc_updatePhoneCallSignalingData: case mtpc_updatePhoneCallSignalingData:
case mtpc_updateGroupCallParticipants: case mtpc_updateGroupCallParticipants:
case mtpc_updateGroupCallChainBlocks:
case mtpc_updateGroupCallConnection: case mtpc_updateGroupCallConnection:
case mtpc_updateGroupCall: { case mtpc_updateGroupCall: {
Core::App().calls().handleUpdate(&session(), update); Core::App().calls().handleUpdate(&session(), update);

View file

@ -235,16 +235,18 @@ void Instance::startOrJoinConferenceCall(
StartConferenceCallArgs args) { StartConferenceCallArgs args) {
destroyCurrentCall(); destroyCurrentCall();
const auto session = &args.call->peer()->session();
auto call = std::make_unique<GroupCall>( auto call = std::make_unique<GroupCall>(
_delegate.get(), _delegate.get(),
Calls::Group::ConferenceInfo{ Calls::Group::ConferenceInfo{
.call = args.call, .call = std::move(args.call),
.e2e = std::move(args.e2e),
.linkSlug = args.linkSlug, .linkSlug = args.linkSlug,
.joinMessageId = args.joinMessageId, .joinMessageId = args.joinMessageId,
}); });
const auto raw = call.get(); const auto raw = call.get();
args.call->peer()->session().account().sessionChanges( session->account().sessionChanges(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
destroyGroupCall(raw); destroyGroupCall(raw);
}, raw->lifetime()); }, raw->lifetime());
@ -547,6 +549,8 @@ void Instance::handleUpdate(
handleGroupCallUpdate(session, update); handleGroupCallUpdate(session, update);
}, [&](const MTPDupdateGroupCallParticipants &data) { }, [&](const MTPDupdateGroupCallParticipants &data) {
handleGroupCallUpdate(session, update); handleGroupCallUpdate(session, update);
}, [&](const MTPDupdateGroupCallChainBlocks &data) {
handleGroupCallUpdate(session, update);
}, [](const auto &) { }, [](const auto &) {
Unexpected("Update type in Calls::Instance::handleUpdate."); Unexpected("Update type in Calls::Instance::handleUpdate.");
}); });
@ -677,6 +681,12 @@ void Instance::handleGroupCallUpdate(
}, [](const MTPDinputGroupCallSlug &) -> CallId { }, [](const MTPDinputGroupCallSlug &) -> CallId {
Unexpected("slug in Instance::handleGroupCallUpdate"); 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 { }, [](const auto &) -> CallId {
Unexpected("Type in Instance::handleGroupCallUpdate."); Unexpected("Type in Instance::handleGroupCallUpdate.");
}); });

View file

@ -45,6 +45,10 @@ namespace tgcalls {
class VideoCaptureInterface; class VideoCaptureInterface;
} // namespace tgcalls } // namespace tgcalls
namespace TdE2E {
class Call;
} // namespace TdE2E
namespace Calls { namespace Calls {
class Call; class Call;
@ -66,6 +70,7 @@ struct StartGroupCallArgs {
struct StartConferenceCallArgs { struct StartConferenceCallArgs {
std::shared_ptr<Data::GroupCall> call; std::shared_ptr<Data::GroupCall> call;
std::shared_ptr<TdE2E::Call> e2e;
QString linkSlug; QString linkSlug;
MsgId joinMessageId; MsgId joinMessageId;
}; };

View file

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/global_shortcuts.h" #include "base/global_shortcuts.h"
#include "base/random.h" #include "base/random.h"
#include "tde2e/tde2e_api.h" #include "tde2e/tde2e_api.h"
#include "tde2e/tde2e_integration.h"
#include "webrtc/webrtc_video_track.h" #include "webrtc/webrtc_video_track.h"
#include "webrtc/webrtc_create_adm.h" #include "webrtc/webrtc_create_adm.h"
#include "webrtc/webrtc_environment.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 kFixSpeakingLargeVideoDuration = 3 * crl::time(1000);
constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums. constexpr auto kFullAsMediumsCount = 4; // 1 Full is like 4 Mediums.
constexpr auto kMaxMediumQualities = 16; // 4 Fulls or 16 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( [[nodiscard]] const Data::GroupCallParticipant *LookupParticipant(
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -603,7 +608,8 @@ GroupCall::GroupCall(
Group::ConferenceInfo conference, Group::ConferenceInfo conference,
const MTPInputGroupCall &inputCall) const MTPInputGroupCall &inputCall)
: _delegate(delegate) : _delegate(delegate)
, _conferenceCall(conference.call) , _conferenceCall(std::move(conference.call))
, _e2e(std::move(conference.e2e))
, _peer(join.peer) , _peer(join.peer)
, _history(_peer->owner().history(_peer)) , _history(_peer->owner().history(_peer))
, _api(&_peer->session().mtp()) , _api(&_peer->session().mtp())
@ -641,8 +647,15 @@ GroupCall::GroupCall(
, _rtmp(join.rtmp) , _rtmp(join.rtmp)
, _rtmpVolume(Group::kDefaultVolume) { , _rtmpVolume(Group::kDefaultVolume) {
if (_conferenceCall) { if (_conferenceCall) {
_e2eState = std::make_unique<TdE2E::CallState>( if (!_e2e) {
TdE2E::CreateCallState()); _e2e = std::make_shared<TdE2E::Call>(
TdE2E::MakeUserId(_peer->session().user()));
}
for (auto i = 0; i != kSubChainsCount; ++i) {
_subchains[i].timer.setCallback([=] {
checkChainBlocksRequest(i);
});
}
} }
_muted.value( _muted.value(
@ -1419,10 +1432,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
? Flag::f_video_stopped ? Flag::f_video_stopped
: Flag(0)) : Flag(0))
| (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag())
| (_e2eState ? Flag::f_public_key : Flag()); | (_e2e ? Flag::f_public_key : Flag());
const auto publicKey = _e2eState
? _e2eState->myKey
: TdE2E::PublicKey();
_api.request(MTPphone_JoinGroupCall( _api.request(MTPphone_JoinGroupCall(
MTP_flags(flags), MTP_flags(flags),
(_conferenceLinkSlug.isEmpty() (_conferenceLinkSlug.isEmpty()
@ -1431,9 +1441,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
MTP_string(_conferenceLinkSlug))), MTP_string(_conferenceLinkSlug))),
joinAs()->input, joinAs()->input,
MTP_string(_joinHash), MTP_string(_joinHash),
MTP_int256( (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()),
MTP_int128(publicKey.a, publicKey.b),
MTP_int128(publicKey.c, publicKey.d)),
MTP_int(_conferenceJoinMessageId.bare), MTP_int(_conferenceJoinMessageId.bare),
MTP_dataJSON(MTP_bytes(json)) MTP_dataJSON(MTP_bytes(json))
)).done([=]( )).done([=](
@ -1470,6 +1478,8 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
real->reloadIfStale(); real->reloadIfStale();
} }
} }
checkChainBlocksRequest(kSubChain0);
checkChainBlocksRequest(kSubChain1);
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_joinState.finish(); _joinState.finish();
@ -1490,6 +1500,40 @@ void GroupCall::rejoin(not_null<PeerData*> 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() { void GroupCall::checkNextJoinAction() {
if (_joinState.action != JoinAction::None) { if (_joinState.action != JoinAction::None) {
return; return;
@ -1640,7 +1684,6 @@ void GroupCall::applyMeInCallLocally() {
: participant : participant
? participant->raisedHandRating ? participant->raisedHandRating
: FindLocalRaisedHandRating(real->participants()); : FindLocalRaisedHandRating(real->participants());
const auto publicKey = _e2eState ? _e2eState->myKey : TdE2E::PublicKey();
const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0)) const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0))
| (lastActive ? Flag::f_active_date : Flag(0)) | (lastActive ? Flag::f_active_date : Flag(0))
| (_joinState.ssrc ? Flag(0) : Flag::f_left) | (_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. | Flag::f_volume_by_admin // Self volume can only be set by admin.
| ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0)) | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0))
| (raisedHandRating > 0 ? Flag::f_raise_hand_rating : 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( real->applyLocalUpdate(
MTP_updateGroupCallParticipants( MTP_updateGroupCallParticipants(
inputCall(), inputCall(),
@ -1667,9 +1710,9 @@ void GroupCall::applyMeInCallLocally() {
MTP_long(raisedHandRating), MTP_long(raisedHandRating),
MTPGroupCallParticipantVideo(), MTPGroupCallParticipantVideo(),
MTPGroupCallParticipantVideo(), MTPGroupCallParticipantVideo(),
MTP_int256( (_e2e
MTP_int128(publicKey.a, publicKey.b), ? TdE2E::PublicKeyToMTP(_e2e->myKey())
MTP_int128(publicKey.c, publicKey.d)))), : MTPint256()))),
MTP_int(0)).c_updateGroupCallParticipants()); MTP_int(0)).c_updateGroupCallParticipants());
} }
@ -2022,6 +2065,8 @@ void GroupCall::handleUpdate(const MTPUpdate &update) {
handleUpdate(data); handleUpdate(data);
}, [&](const MTPDupdateGroupCallParticipants &data) { }, [&](const MTPDupdateGroupCallParticipants &data) {
handleUpdate(data); handleUpdate(data);
}, [&](const MTPDupdateGroupCallChainBlocks &data) {
handleUpdate(data);
}, [](const auto &) { }, [](const auto &) {
Unexpected("Type in Instance::applyGroupCallUpdateChecked."); 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() { void GroupCall::applyQueuedSelfUpdates() {
const auto weak = base::make_weak(this); const auto weak = base::make_weak(this);
while (weak while (weak

View file

@ -44,7 +44,7 @@ class GroupCall;
namespace TdE2E { namespace TdE2E {
struct ParticipantState; struct ParticipantState;
struct CallState; class Call;
} // namespace TdE2E } // namespace TdE2E
namespace Calls { namespace Calls {
@ -437,6 +437,7 @@ private:
struct SinkPointer; struct SinkPointer;
static constexpr uint32 kDisabledSsrc = uint32(-1); static constexpr uint32 kDisabledSsrc = uint32(-1);
static constexpr int kSubChainsCount = 2;
struct LoadingPart { struct LoadingPart {
std::shared_ptr<LoadPartTask> task; std::shared_ptr<LoadPartTask> task;
@ -475,6 +476,12 @@ private:
ssrc = updatedSsrc; 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) { friend inline constexpr bool is_flag_type(SendUpdateType) {
return true; return true;
@ -507,6 +514,7 @@ private:
void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data);
void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCall &data);
void handleUpdate(const MTPDupdateGroupCallParticipants &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data);
void handleUpdate(const MTPDupdateGroupCallChainBlocks &data);
bool tryCreateController(); bool tryCreateController();
void destroyController(); void destroyController();
bool tryCreateScreencast(); bool tryCreateScreencast();
@ -535,6 +543,7 @@ private:
void rejoinPresentation(); void rejoinPresentation();
void leavePresentation(); void leavePresentation();
void checkNextJoinAction(); void checkNextJoinAction();
void checkChainBlocksRequest(int subchain);
void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data);
void setInstanceConnected(tgcalls::GroupNetworkState networkState); void setInstanceConnected(tgcalls::GroupNetworkState networkState);
@ -593,6 +602,8 @@ private:
const not_null<Delegate*> _delegate; const not_null<Delegate*> _delegate;
const std::shared_ptr<Data::GroupCall> _conferenceCall; const std::shared_ptr<Data::GroupCall> _conferenceCall;
std::shared_ptr<TdE2E::Call> _e2e;
not_null<PeerData*> _peer; // Can change in legacy group migration. not_null<PeerData*> _peer; // Can change in legacy group migration.
rpl::event_stream<PeerData*> _peerStream; rpl::event_stream<PeerData*> _peerStream;
not_null<History*> _history; // Can change in legacy group migration. not_null<History*> _history; // Can change in legacy group migration.
@ -624,8 +635,6 @@ private:
int64 _serverTimeMs = 0; int64 _serverTimeMs = 0;
crl::time _serverTimeMsGotAt = 0; crl::time _serverTimeMsGotAt = 0;
std::unique_ptr<TdE2E::CallState> _e2eState;
QString _rtmpUrl; QString _rtmpUrl;
QString _rtmpKey; QString _rtmpKey;
@ -710,6 +719,8 @@ private:
bool _reloadedStaleCall = false; bool _reloadedStaleCall = false;
int _rtmpVolume = 0; int _rtmpVolume = 0;
SubChainState _subchains[kSubChainsCount];
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
}; };

View file

@ -19,6 +19,10 @@ namespace Ui {
class GenericBox; class GenericBox;
} // namespace Ui } // namespace Ui
namespace TdE2E {
class Call;
} // namespace TdE2E
namespace Calls::Group { namespace Calls::Group {
constexpr auto kDefaultVolume = 10000; constexpr auto kDefaultVolume = 10000;
@ -67,6 +71,7 @@ struct JoinInfo {
struct ConferenceInfo { struct ConferenceInfo {
std::shared_ptr<Data::GroupCall> call; std::shared_ptr<Data::GroupCall> call;
std::shared_ptr<TdE2E::Call> e2e;
QString linkSlug; QString linkSlug;
MsgId joinMessageId; MsgId joinMessageId;
}; };

View file

@ -19,6 +19,7 @@ struct ParticipantVideoParams;
namespace TdE2E { namespace TdE2E {
struct ParticipantState; struct ParticipantState;
struct UserId;
} // namespace TdE2E } // namespace TdE2E
namespace Data { namespace Data {

View file

@ -12,20 +12,77 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <tde2e/td/e2e/e2e_api.h> #include <tde2e/td/e2e/e2e_api.h>
namespace TdE2E { 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(); const auto id = tde2e_api::key_generate_temporary_private_key();
Assert(id.is_ok()); 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.is_ok());
Assert(key.value().size() == sizeof(_myKey));
memcpy(&_myKey, key.value().data(), sizeof(_myKey));
}
auto result = CallState{ PublicKey Call::myKey() const {
.myKeyId = PrivateKeyId{ .v = uint64(id.value()) }, return _myKey;
}
Block Call::makeZeroBlock() const {
const auto publicKeyView = std::string_view{
reinterpret_cast<const char*>(&_myKey),
sizeof(_myKey),
}; };
Assert(key.value().size() == sizeof(result.myKey)); const auto publicKeyId = tde2e_api::key_from_public_key(publicKeyView);
memcpy(&result.myKey, key.value().data(), 32); 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 } // namespace TdE2E

View file

@ -11,10 +11,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace TdE2E { namespace TdE2E {
struct UserId {
uint64 v = 0;
};
struct PrivateKeyId { struct PrivateKeyId {
uint64 v = 0; uint64 v = 0;
}; };
struct CallId {
uint64 v = 0;
};
struct PublicKey { struct PublicKey {
uint64 a = 0; uint64 a = 0;
uint64 b = 0; uint64 b = 0;
@ -23,15 +31,36 @@ struct PublicKey {
}; };
struct ParticipantState { struct ParticipantState {
uint64 id = 0; UserId id;
PublicKey key; PublicKey key;
}; };
struct CallState { struct Block {
PrivateKeyId myKeyId; QByteArray data;
PublicKey myKey;
}; };
[[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 } // namespace TdE2E

View file

@ -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<UserData*> 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

View file

@ -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<UserData*> user);
[[nodiscard]] UserId MakeUserId(::UserId id);
[[nodiscard]] MTPint256 PublicKeyToMTP(const PublicKey &key);
} // namespace TdE2E

View file

@ -14,17 +14,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peer_list_controllers.h" #include "boxes/peer_list_controllers.h"
#include "boxes/premium_preview_box.h" #include "boxes/premium_preview_box.h"
#include "calls/calls_box_controller.h" #include "calls/calls_box_controller.h"
#include "calls/calls_instance.h"
#include "core/application.h" #include "core/application.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_group_call.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "info/info_memento.h" #include "info/info_memento.h"
#include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_badge.h"
#include "info/profile/info_profile_emoji_status_panel.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 "info/stories/info_stories_widget.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_account.h" #include "main/main_account.h"
@ -38,6 +41,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h" #include "storage/localstorage.h"
#include "storage/storage_account.h" #include "storage/storage_account.h"
#include "support/support_templates.h" #include "support/support_templates.h"
#include "tde2e/tde2e_api.h"
#include "tde2e/tde2e_integration.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/controls/swipe_handler.h" #include "ui/controls/swipe_handler.h"
@ -92,6 +97,66 @@ constexpr auto kPlayStatusLimit = 2;
|| (now.month() == 1 && now.day() == 1); || (now.month() == 1 && now.day() == 1);
} }
[[nodiscard]] not_null<Ui::SettingsButton*> AddCreateCallLinkButton(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Fn<void()> done) {
const auto result = container->add(object_ptr<Ui::SettingsButton>(
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<Info::Profile::FloatingIcon>(
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<bool>();
result->setClickedCallback([=] {
if (*creating) {
return;
}
*creating = true;
auto e2e = std::make_shared<TdE2E::Call>(
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<Data::GroupCall>(
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::SessionController*> window) { void ShowCallsBox(not_null<Window::SessionController*> window) {
struct State { struct State {
State(not_null<Window::SessionController*> window) State(not_null<Window::SessionController*> window)
@ -129,6 +194,17 @@ void ShowCallsBox(not_null<Window::SessionController*> window) {
Ui::AddDivider(groupCalls->entity()); Ui::AddDivider(groupCalls->entity());
Ui::AddSkip(groupCalls->entity()); Ui::AddSkip(groupCalls->entity());
const auto button = AddCreateCallLinkButton(
box->verticalLayout(),
window,
crl::guard(box, [=] { box->closeBox(); }));
button->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::Enter);
}) | rpl::start_with_next([=] {
state->callsDelegate.peerListMouseLeftGeometry();
}, button->lifetime());
const auto content = box->addRow( const auto content = box->addRow(
object_ptr<PeerListContent>(box, &state->callsController), object_ptr<PeerListContent>(box, &state->callsController),
{}); {});