1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "calls/calls_instance.h"
9
10 #include "calls/calls_call.h"
11 #include "calls/group/calls_group_common.h"
12 #include "calls/group/calls_choose_join_as.h"
13 #include "calls/group/calls_group_call.h"
14 #include "mtproto/mtproto_dh_utils.h"
15 #include "core/application.h"
16 #include "main/main_session.h"
17 #include "main/main_account.h"
18 #include "apiwrap.h"
19 #include "lang/lang_keys.h"
20 #include "ui/boxes/confirm_box.h"
21 #include "calls/group/calls_group_call.h"
22 #include "calls/group/calls_group_panel.h"
23 #include "calls/calls_call.h"
24 #include "calls/calls_panel.h"
25 #include "data/data_user.h"
26 #include "data/data_group_call.h"
27 #include "data/data_channel.h"
28 #include "data/data_chat.h"
29 #include "data/data_session.h"
30 #include "media/audio/media_audio_track.h"
31 #include "platform/platform_specific.h"
32 #include "ui/toast/toast.h"
33 #include "base/unixtime.h"
34 #include "mtproto/mtproto_config.h"
35 #include "app.h" // App::quitting
36
37 #include <tgcalls/VideoCaptureInterface.h>
38 #include <tgcalls/StaticThreads.h>
39
40 namespace Calls {
41 namespace {
42
43 constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * crl::time(1000);
44
45 using CallSound = Call::Delegate::CallSound;
46 using GroupCallSound = GroupCall::Delegate::GroupCallSound;
47
48 } // namespace
49
50 class Instance::Delegate final
51 : public Call::Delegate
52 , public GroupCall::Delegate {
53 public:
54 explicit Delegate(not_null<Instance*> instance);
55
56 DhConfig getDhConfig() const override;
57
58 void callFinished(not_null<Call*> call) override;
59 void callFailed(not_null<Call*> call) override;
60 void callRedial(not_null<Call*> call) override;
61 void callRequestPermissionsOrFail(
62 Fn<void()> onSuccess,
63 bool video) override;
64 void callPlaySound(CallSound sound) override;
65 auto callGetVideoCapture(
66 const QString &deviceId,
67 bool isScreenCapture)
68 -> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
69
70 void groupCallFinished(not_null<GroupCall*> call) override;
71 void groupCallFailed(not_null<GroupCall*> call) override;
72 void groupCallRequestPermissionsOrFail(Fn<void()> onSuccess) override;
73 void groupCallPlaySound(GroupCallSound sound) override;
74 auto groupCallGetVideoCapture(const QString &deviceId)
75 -> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
76 FnMut<void()> groupCallAddAsyncWaiter() override;
77
78 private:
79 const not_null<Instance*> _instance;
80
81 };
82
Delegate(not_null<Instance * > instance)83 Instance::Delegate::Delegate(not_null<Instance*> instance)
84 : _instance(instance) {
85 }
86
getDhConfig() const87 DhConfig Instance::Delegate::getDhConfig() const {
88 return *_instance->_cachedDhConfig;
89 }
90
callFinished(not_null<Call * > call)91 void Instance::Delegate::callFinished(not_null<Call*> call) {
92 crl::on_main(call, [=] {
93 _instance->destroyCall(call);
94 });
95 }
96
callFailed(not_null<Call * > call)97 void Instance::Delegate::callFailed(not_null<Call*> call) {
98 crl::on_main(call, [=] {
99 _instance->destroyCall(call);
100 });
101 }
102
callRedial(not_null<Call * > call)103 void Instance::Delegate::callRedial(not_null<Call*> call) {
104 if (_instance->_currentCall.get() == call) {
105 _instance->refreshDhConfig();
106 }
107 }
108
callRequestPermissionsOrFail(Fn<void ()> onSuccess,bool video)109 void Instance::Delegate::callRequestPermissionsOrFail(
110 Fn<void()> onSuccess,
111 bool video) {
112 _instance->requestPermissionsOrFail(std::move(onSuccess), video);
113 }
114
callPlaySound(CallSound sound)115 void Instance::Delegate::callPlaySound(CallSound sound) {
116 _instance->playSoundOnce([&] {
117 switch (sound) {
118 case CallSound::Busy: return "call_busy";
119 case CallSound::Ended: return "call_end";
120 case CallSound::Connecting: return "call_connect";
121 }
122 Unexpected("CallSound in Instance::callPlaySound.");
123 }());
124 }
125
callGetVideoCapture(const QString & deviceId,bool isScreenCapture)126 auto Instance::Delegate::callGetVideoCapture(
127 const QString &deviceId,
128 bool isScreenCapture)
129 -> std::shared_ptr<tgcalls::VideoCaptureInterface> {
130 return _instance->getVideoCapture(deviceId, isScreenCapture);
131 }
132
groupCallFinished(not_null<GroupCall * > call)133 void Instance::Delegate::groupCallFinished(not_null<GroupCall*> call) {
134 crl::on_main(call, [=] {
135 _instance->destroyGroupCall(call);
136 });
137 }
138
groupCallFailed(not_null<GroupCall * > call)139 void Instance::Delegate::groupCallFailed(not_null<GroupCall*> call) {
140 crl::on_main(call, [=] {
141 _instance->destroyGroupCall(call);
142 });
143 }
144
groupCallRequestPermissionsOrFail(Fn<void ()> onSuccess)145 void Instance::Delegate::groupCallRequestPermissionsOrFail(
146 Fn<void()> onSuccess) {
147 _instance->requestPermissionsOrFail(std::move(onSuccess), false);
148 }
149
groupCallPlaySound(GroupCallSound sound)150 void Instance::Delegate::groupCallPlaySound(GroupCallSound sound) {
151 _instance->playSoundOnce([&] {
152 switch (sound) {
153 case GroupCallSound::Started: return "group_call_start";
154 case GroupCallSound::Ended: return "group_call_end";
155 case GroupCallSound::AllowedToSpeak: return "group_call_allowed";
156 case GroupCallSound::Connecting: return "group_call_connect";
157 }
158 Unexpected("GroupCallSound in Instance::groupCallPlaySound.");
159 }());
160 }
161
groupCallGetVideoCapture(const QString & deviceId)162 auto Instance::Delegate::groupCallGetVideoCapture(const QString &deviceId)
163 -> std::shared_ptr<tgcalls::VideoCaptureInterface> {
164 return _instance->getVideoCapture(deviceId, false);
165 }
166
groupCallAddAsyncWaiter()167 FnMut<void()> Instance::Delegate::groupCallAddAsyncWaiter() {
168 return _instance->addAsyncWaiter();
169 }
170
Instance()171 Instance::Instance()
172 : _delegate(std::make_unique<Delegate>(this))
173 , _cachedDhConfig(std::make_unique<DhConfig>())
174 , _chooseJoinAs(std::make_unique<Group::ChooseJoinAsProcess>()) {
175 }
176
~Instance()177 Instance::~Instance() {
178 destroyCurrentCall();
179
180 while (!_asyncWaiters.empty()) {
181 _asyncWaiters.front()->acquire();
182 _asyncWaiters.erase(_asyncWaiters.begin());
183 }
184 }
185
startOutgoingCall(not_null<UserData * > user,bool video)186 void Instance::startOutgoingCall(not_null<UserData*> user, bool video) {
187 if (activateCurrentCall()) {
188 return;
189 }
190 if (user->callsStatus() == UserData::CallsStatus::Private) {
191 // Request full user once more to refresh the setting in case it was changed.
192 user->session().api().requestFullPeer(user);
193 Ui::show(Box<Ui::InformBox>(
194 tr::lng_call_error_not_available(tr::now, lt_user, user->name)));
195 return;
196 }
197 requestPermissionsOrFail(crl::guard(this, [=] {
198 createCall(user, Call::Type::Outgoing, video);
199 }), video);
200 }
201
startOrJoinGroupCall(not_null<PeerData * > peer,const QString & joinHash,bool confirmNeeded)202 void Instance::startOrJoinGroupCall(
203 not_null<PeerData*> peer,
204 const QString &joinHash,
205 bool confirmNeeded) {
206 const auto context = confirmNeeded
207 ? Group::ChooseJoinAsProcess::Context::JoinWithConfirm
208 : peer->groupCall()
209 ? Group::ChooseJoinAsProcess::Context::Join
210 : Group::ChooseJoinAsProcess::Context::Create;
211 _chooseJoinAs->start(peer, context, [=](object_ptr<Ui::BoxContent> box) {
212 Ui::show(std::move(box), Ui::LayerOption::KeepOther);
213 }, [=](QString text) {
214 Ui::Toast::Show(text);
215 }, [=](Group::JoinInfo info) {
216 const auto call = info.peer->groupCall();
217 info.joinHash = joinHash;
218 createGroupCall(
219 std::move(info),
220 call ? call->input() : MTP_inputGroupCall(MTPlong(), MTPlong()));
221 });
222 }
223
ensureSoundLoaded(const QString & key)224 not_null<Media::Audio::Track*> Instance::ensureSoundLoaded(
225 const QString &key) {
226 const auto i = _tracks.find(key);
227 if (i != end(_tracks)) {
228 return i->second.get();
229 }
230 const auto result = _tracks.emplace(
231 key,
232 Media::Audio::Current().createTrack()).first->second.get();
233 result->fillFromFile(Core::App().settings().getSoundPath(key));
234 return result;
235 }
236
playSoundOnce(const QString & key)237 void Instance::playSoundOnce(const QString &key) {
238 ensureSoundLoaded(key)->playOnce();
239 }
240
destroyCall(not_null<Call * > call)241 void Instance::destroyCall(not_null<Call*> call) {
242 if (_currentCall.get() == call) {
243 _currentCallPanel->closeBeforeDestroy();
244 _currentCallPanel = nullptr;
245
246 auto taken = base::take(_currentCall);
247 _currentCallChanges.fire(nullptr);
248 taken.reset();
249
250 if (App::quitting()) {
251 LOG(("Calls::Instance doesn't prevent quit any more."));
252 }
253 Core::App().quitPreventFinished();
254 }
255 }
256
createCall(not_null<UserData * > user,Call::Type type,bool video)257 void Instance::createCall(not_null<UserData*> user, Call::Type type, bool video) {
258 auto call = std::make_unique<Call>(_delegate.get(), user, type, video);
259 const auto raw = call.get();
260
261 user->session().account().sessionChanges(
262 ) | rpl::start_with_next([=] {
263 destroyCall(raw);
264 }, raw->lifetime());
265
266 if (_currentCall) {
267 _currentCallPanel->replaceCall(raw);
268 std::swap(_currentCall, call);
269 call->hangup();
270 } else {
271 _currentCallPanel = std::make_unique<Panel>(raw);
272 _currentCall = std::move(call);
273 }
274 _currentCallChanges.fire_copy(raw);
275 refreshServerConfig(&user->session());
276 refreshDhConfig();
277 }
278
destroyGroupCall(not_null<GroupCall * > call)279 void Instance::destroyGroupCall(not_null<GroupCall*> call) {
280 if (_currentGroupCall.get() == call) {
281 _currentGroupCallPanel->closeBeforeDestroy();
282 _currentGroupCallPanel = nullptr;
283
284 auto taken = base::take(_currentGroupCall);
285 _currentGroupCallChanges.fire(nullptr);
286 taken.reset();
287
288 if (App::quitting()) {
289 LOG(("Calls::Instance doesn't prevent quit any more."));
290 }
291 Core::App().quitPreventFinished();
292 }
293 }
294
createGroupCall(Group::JoinInfo info,const MTPInputGroupCall & inputCall)295 void Instance::createGroupCall(
296 Group::JoinInfo info,
297 const MTPInputGroupCall &inputCall) {
298 destroyCurrentCall();
299
300 auto call = std::make_unique<GroupCall>(
301 _delegate.get(),
302 std::move(info),
303 inputCall);
304 const auto raw = call.get();
305
306 info.peer->session().account().sessionChanges(
307 ) | rpl::start_with_next([=] {
308 destroyGroupCall(raw);
309 }, raw->lifetime());
310
311 _currentGroupCallPanel = std::make_unique<Group::Panel>(raw);
312 _currentGroupCall = std::move(call);
313 _currentGroupCallChanges.fire_copy(raw);
314 }
315
refreshDhConfig()316 void Instance::refreshDhConfig() {
317 Expects(_currentCall != nullptr);
318
319 const auto weak = base::make_weak(_currentCall);
320 _currentCall->user()->session().api().request(MTPmessages_GetDhConfig(
321 MTP_int(_cachedDhConfig->version),
322 MTP_int(MTP::ModExpFirst::kRandomPowerSize)
323 )).done([=](const MTPmessages_DhConfig &result) {
324 const auto call = weak.get();
325 const auto random = updateDhConfig(result);
326 if (!call) {
327 return;
328 }
329 if (!random.empty()) {
330 Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize);
331 call->start(random);
332 } else {
333 _delegate->callFailed(call);
334 }
335 }).fail([=](const MTP::Error &error) {
336 const auto call = weak.get();
337 if (!call) {
338 return;
339 }
340 _delegate->callFailed(call);
341 }).send();
342 }
343
updateDhConfig(const MTPmessages_DhConfig & data)344 bytes::const_span Instance::updateDhConfig(
345 const MTPmessages_DhConfig &data) {
346 const auto validRandom = [](const QByteArray & random) {
347 if (random.size() != MTP::ModExpFirst::kRandomPowerSize) {
348 return false;
349 }
350 return true;
351 };
352 return data.match([&](const MTPDmessages_dhConfig &data)
353 -> bytes::const_span {
354 auto primeBytes = bytes::make_vector(data.vp().v);
355 if (!MTP::IsPrimeAndGood(primeBytes, data.vg().v)) {
356 LOG(("API Error: bad p/g received in dhConfig."));
357 return {};
358 } else if (!validRandom(data.vrandom().v)) {
359 return {};
360 }
361 _cachedDhConfig->g = data.vg().v;
362 _cachedDhConfig->p = std::move(primeBytes);
363 _cachedDhConfig->version = data.vversion().v;
364 return bytes::make_span(data.vrandom().v);
365 }, [&](const MTPDmessages_dhConfigNotModified &data)
366 -> bytes::const_span {
367 if (!_cachedDhConfig->g || _cachedDhConfig->p.empty()) {
368 LOG(("API Error: dhConfigNotModified on zero version."));
369 return {};
370 } else if (!validRandom(data.vrandom().v)) {
371 return {};
372 }
373 return bytes::make_span(data.vrandom().v);
374 });
375 }
376
refreshServerConfig(not_null<Main::Session * > session)377 void Instance::refreshServerConfig(not_null<Main::Session*> session) {
378 if (_serverConfigRequestSession) {
379 return;
380 }
381 if (_lastServerConfigUpdateTime
382 && ((crl::now() - _lastServerConfigUpdateTime)
383 < kServerConfigUpdateTimeoutMs)) {
384 return;
385 }
386 _serverConfigRequestSession = session;
387 session->api().request(MTPphone_GetCallConfig(
388 )).done([=](const MTPDataJSON &result) {
389 _serverConfigRequestSession = nullptr;
390 _lastServerConfigUpdateTime = crl::now();
391
392 const auto &json = result.c_dataJSON().vdata().v;
393 UpdateConfig(std::string(json.data(), json.size()));
394 }).fail([=](const MTP::Error &error) {
395 _serverConfigRequestSession = nullptr;
396 }).send();
397 }
398
handleUpdate(not_null<Main::Session * > session,const MTPUpdate & update)399 void Instance::handleUpdate(
400 not_null<Main::Session*> session,
401 const MTPUpdate &update) {
402 update.match([&](const MTPDupdatePhoneCall &data) {
403 handleCallUpdate(session, data.vphone_call());
404 }, [&](const MTPDupdatePhoneCallSignalingData &data) {
405 handleSignalingData(session, data);
406 }, [&](const MTPDupdateGroupCall &data) {
407 handleGroupCallUpdate(session, update);
408 }, [&](const MTPDupdateGroupCallConnection &data) {
409 handleGroupCallUpdate(session, update);
410 }, [&](const MTPDupdateGroupCallParticipants &data) {
411 handleGroupCallUpdate(session, update);
412 }, [](const auto &) {
413 Unexpected("Update type in Calls::Instance::handleUpdate.");
414 });
415 }
416
showInfoPanel(not_null<Call * > call)417 void Instance::showInfoPanel(not_null<Call*> call) {
418 if (_currentCall.get() == call) {
419 _currentCallPanel->showAndActivate();
420 }
421 }
422
showInfoPanel(not_null<GroupCall * > call)423 void Instance::showInfoPanel(not_null<GroupCall*> call) {
424 if (_currentGroupCall.get() == call) {
425 _currentGroupCallPanel->showAndActivate();
426 }
427 }
428
setCurrentAudioDevice(bool input,const QString & deviceId)429 void Instance::setCurrentAudioDevice(bool input, const QString &deviceId) {
430 if (input) {
431 Core::App().settings().setCallInputDeviceId(deviceId);
432 } else {
433 Core::App().settings().setCallOutputDeviceId(deviceId);
434 }
435 Core::App().saveSettingsDelayed();
436 if (const auto call = currentCall()) {
437 call->setCurrentAudioDevice(input, deviceId);
438 } else if (const auto group = currentGroupCall()) {
439 group->setCurrentAudioDevice(input, deviceId);
440 }
441 }
442
addAsyncWaiter()443 FnMut<void()> Instance::addAsyncWaiter() {
444 auto semaphore = std::make_unique<crl::semaphore>();
445 const auto raw = semaphore.get();
446 const auto weak = base::make_weak(this);
447 _asyncWaiters.emplace(std::move(semaphore));
448 return [raw, weak] {
449 raw->release();
450 crl::on_main(weak, [raw, weak] {
451 auto &waiters = weak->_asyncWaiters;
452 auto wrapped = std::unique_ptr<crl::semaphore>(raw);
453 const auto i = waiters.find(wrapped);
454 wrapped.release();
455
456 if (i != end(waiters)) {
457 waiters.erase(i);
458 }
459 });
460 };
461 }
462
isQuitPrevent()463 bool Instance::isQuitPrevent() {
464 if (!_currentCall || _currentCall->isIncomingWaiting()) {
465 return false;
466 }
467 _currentCall->hangup();
468 if (!_currentCall) {
469 return false;
470 }
471 LOG(("Calls::Instance prevents quit, hanging up a call..."));
472 return true;
473 }
474
handleCallUpdate(not_null<Main::Session * > session,const MTPPhoneCall & call)475 void Instance::handleCallUpdate(
476 not_null<Main::Session*> session,
477 const MTPPhoneCall &call) {
478 if (call.type() == mtpc_phoneCallRequested) {
479 auto &phoneCall = call.c_phoneCallRequested();
480 auto user = session->data().userLoaded(phoneCall.vadmin_id());
481 if (!user) {
482 LOG(("API Error: User not loaded for phoneCallRequested."));
483 } else if (user->isSelf()) {
484 LOG(("API Error: Self found in phoneCallRequested."));
485 } else if (_currentCall
486 && _currentCall->user() == user
487 && _currentCall->id() == phoneCall.vid().v) {
488 // May be a repeated phoneCallRequested update from getDifference.
489 return;
490 }
491 const auto &config = session->serverConfig();
492 if (inCall() || inGroupCall() || !user || user->isSelf()) {
493 const auto flags = phoneCall.is_video()
494 ? MTPphone_DiscardCall::Flag::f_video
495 : MTPphone_DiscardCall::Flag(0);
496 session->api().request(MTPphone_DiscardCall(
497 MTP_flags(flags),
498 MTP_inputPhoneCall(phoneCall.vid(), phoneCall.vaccess_hash()),
499 MTP_int(0),
500 MTP_phoneCallDiscardReasonBusy(),
501 MTP_long(0)
502 )).send();
503 } else if (phoneCall.vdate().v + (config.callRingTimeoutMs / 1000)
504 < base::unixtime::now()) {
505 LOG(("Ignoring too old call."));
506 } else if (Core::App().settings().disableCalls()) {
507 LOG(("Ignoring call because of 'accept calls' settings."));
508 } else {
509 createCall(user, Call::Type::Incoming, phoneCall.is_video());
510 _currentCall->handleUpdate(call);
511 }
512 } else if (!_currentCall
513 || (&_currentCall->user()->session() != session)
514 || !_currentCall->handleUpdate(call)) {
515 DEBUG_LOG(("API Warning: unexpected phone call update %1").arg(call.type()));
516 }
517 }
518
handleGroupCallUpdate(not_null<Main::Session * > session,const MTPUpdate & update)519 void Instance::handleGroupCallUpdate(
520 not_null<Main::Session*> session,
521 const MTPUpdate &update) {
522 if (_currentGroupCall
523 && (&_currentGroupCall->peer()->session() == session)) {
524 update.match([&](const MTPDupdateGroupCall &data) {
525 _currentGroupCall->handlePossibleCreateOrJoinResponse(data);
526 }, [&](const MTPDupdateGroupCallConnection &data) {
527 _currentGroupCall->handlePossibleCreateOrJoinResponse(data);
528 }, [](const auto &) {
529 });
530 }
531
532 if (update.type() == mtpc_updateGroupCallConnection) {
533 return;
534 }
535 const auto callId = update.match([](const MTPDupdateGroupCall &data) {
536 return data.vcall().match([](const auto &data) {
537 return data.vid().v;
538 });
539 }, [](const MTPDupdateGroupCallParticipants &data) {
540 return data.vcall().match([&](const MTPDinputGroupCall &data) {
541 return data.vid().v;
542 });
543 }, [](const auto &) -> CallId {
544 Unexpected("Type in Instance::handleGroupCallUpdate.");
545 });
546 if (const auto existing = session->data().groupCall(callId)) {
547 existing->enqueueUpdate(update);
548 } else {
549 applyGroupCallUpdateChecked(session, update);
550 }
551 }
552
applyGroupCallUpdateChecked(not_null<Main::Session * > session,const MTPUpdate & update)553 void Instance::applyGroupCallUpdateChecked(
554 not_null<Main::Session*> session,
555 const MTPUpdate &update) {
556 if (_currentGroupCall
557 && (&_currentGroupCall->peer()->session() == session)) {
558 _currentGroupCall->handleUpdate(update);
559 }
560 }
561
handleSignalingData(not_null<Main::Session * > session,const MTPDupdatePhoneCallSignalingData & data)562 void Instance::handleSignalingData(
563 not_null<Main::Session*> session,
564 const MTPDupdatePhoneCallSignalingData &data) {
565 if (!_currentCall
566 || (&_currentCall->user()->session() != session)
567 || !_currentCall->handleSignalingData(data)) {
568 DEBUG_LOG(("API Warning: unexpected call signaling data %1"
569 ).arg(data.vphone_call_id().v));
570 }
571 }
572
inCall() const573 bool Instance::inCall() const {
574 if (!_currentCall) {
575 return false;
576 }
577 const auto state = _currentCall->state();
578 return (state != Call::State::Busy);
579 }
580
inGroupCall() const581 bool Instance::inGroupCall() const {
582 if (!_currentGroupCall) {
583 return false;
584 }
585 const auto state = _currentGroupCall->state();
586 return (state != GroupCall::State::HangingUp)
587 && (state != GroupCall::State::Ended)
588 && (state != GroupCall::State::FailedHangingUp)
589 && (state != GroupCall::State::Failed);
590 }
591
destroyCurrentCall()592 void Instance::destroyCurrentCall() {
593 if (const auto current = currentCall()) {
594 current->hangup();
595 if (const auto still = currentCall()) {
596 destroyCall(still);
597 }
598 }
599 if (const auto current = currentGroupCall()) {
600 current->hangup();
601 if (const auto still = currentGroupCall()) {
602 destroyGroupCall(still);
603 }
604 }
605 }
606
hasActivePanel(not_null<Main::Session * > session) const607 bool Instance::hasActivePanel(not_null<Main::Session*> session) const {
608 if (inCall()) {
609 return (&_currentCall->user()->session() == session)
610 && _currentCallPanel->isActive();
611 } else if (inGroupCall()) {
612 return (&_currentGroupCall->peer()->session() == session)
613 && _currentGroupCallPanel->isActive();
614 }
615 return false;
616 }
617
activateCurrentCall(const QString & joinHash)618 bool Instance::activateCurrentCall(const QString &joinHash) {
619 if (inCall()) {
620 _currentCallPanel->showAndActivate();
621 return true;
622 } else if (inGroupCall()) {
623 if (!joinHash.isEmpty()) {
624 _currentGroupCall->rejoinWithHash(joinHash);
625 }
626 _currentGroupCallPanel->showAndActivate();
627 return true;
628 }
629 return false;
630 }
631
minimizeCurrentActiveCall()632 bool Instance::minimizeCurrentActiveCall() {
633 if (inCall() && _currentCallPanel->isActive()) {
634 _currentCallPanel->minimize();
635 return true;
636 } else if (inGroupCall() && _currentGroupCallPanel->isActive()) {
637 _currentGroupCallPanel->minimize();
638 return true;
639 }
640 return false;
641 }
642
closeCurrentActiveCall()643 bool Instance::closeCurrentActiveCall() {
644 if (inGroupCall() && _currentGroupCallPanel->isActive()) {
645 _currentGroupCallPanel->close();
646 return true;
647 }
648 return false;
649 }
650
currentCall() const651 Call *Instance::currentCall() const {
652 return _currentCall.get();
653 }
654
currentCallValue() const655 rpl::producer<Call*> Instance::currentCallValue() const {
656 return _currentCallChanges.events_starting_with(currentCall());
657 }
658
currentGroupCall() const659 GroupCall *Instance::currentGroupCall() const {
660 return _currentGroupCall.get();
661 }
662
currentGroupCallValue() const663 rpl::producer<GroupCall*> Instance::currentGroupCallValue() const {
664 return _currentGroupCallChanges.events_starting_with(currentGroupCall());
665 }
666
requestPermissionsOrFail(Fn<void ()> onSuccess,bool video)667 void Instance::requestPermissionsOrFail(Fn<void()> onSuccess, bool video) {
668 using Type = Platform::PermissionType;
669 requestPermissionOrFail(Type::Microphone, [=] {
670 auto callback = [=] { crl::on_main(onSuccess); };
671 if (video) {
672 requestPermissionOrFail(Type::Camera, std::move(callback));
673 } else {
674 callback();
675 }
676 });
677 }
678
requestPermissionOrFail(Platform::PermissionType type,Fn<void ()> onSuccess)679 void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn<void()> onSuccess) {
680 using Status = Platform::PermissionStatus;
681 const auto status = Platform::GetPermissionStatus(type);
682 if (status == Status::Granted) {
683 onSuccess();
684 } else if (status == Status::CanRequest) {
685 Platform::RequestPermission(type, crl::guard(this, [=](Status status) {
686 if (status == Status::Granted) {
687 crl::on_main(onSuccess);
688 } else {
689 if (_currentCall) {
690 _currentCall->hangup();
691 }
692 }
693 }));
694 } else {
695 if (inCall()) {
696 _currentCall->hangup();
697 }
698 if (inGroupCall()) {
699 _currentGroupCall->hangup();
700 }
701 Ui::show(Box<Ui::ConfirmBox>(
702 tr::lng_no_mic_permission(tr::now),
703 tr::lng_menu_settings(tr::now),
704 crl::guard(this, [=] {
705 Platform::OpenSystemSettingsForPermission(type);
706 Ui::hideLayer();
707 })));
708 }
709 }
710
getVideoCapture(std::optional<QString> deviceId,bool isScreenCapture)711 std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
712 std::optional<QString> deviceId,
713 bool isScreenCapture) {
714 if (auto result = _videoCapture.lock()) {
715 if (deviceId) {
716 result->switchToDevice(
717 (deviceId->isEmpty()
718 ? Core::App().settings().callVideoInputDeviceId()
719 : *deviceId).toStdString(),
720 isScreenCapture);
721 }
722 return result;
723 }
724 const auto startDeviceId = (deviceId && !deviceId->isEmpty())
725 ? *deviceId
726 : Core::App().settings().callVideoInputDeviceId();
727 auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>(
728 tgcalls::VideoCaptureInterface::Create(
729 tgcalls::StaticThreads::getThreads(),
730 startDeviceId.toStdString()));
731 _videoCapture = result;
732 return result;
733 }
734
735 } // namespace Calls
736