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