calls_group_settings.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "calls/group/calls_group_settings.h"
  8. #include "calls/group/calls_group_call.h"
  9. #include "calls/group/calls_group_menu.h" // LeaveBox.
  10. #include "calls/group/calls_group_common.h"
  11. #include "calls/group/calls_choose_join_as.h"
  12. #include "calls/group/calls_volume_item.h"
  13. #include "calls/calls_instance.h"
  14. #include "ui/widgets/level_meter.h"
  15. #include "ui/widgets/continuous_sliders.h"
  16. #include "ui/widgets/checkbox.h"
  17. #include "ui/widgets/fields/input_field.h"
  18. #include "ui/widgets/popup_menu.h"
  19. #include "ui/wrap/slide_wrap.h"
  20. #include "ui/text/text_utilities.h"
  21. #include "ui/vertical_list.h"
  22. #include "lang/lang_keys.h"
  23. #include "boxes/share_box.h"
  24. #include "history/view/history_view_schedule_box.h"
  25. #include "history/history_item_helpers.h" // GetErrorForSending.
  26. #include "history/history.h"
  27. #include "data/data_histories.h"
  28. #include "data/data_session.h"
  29. #include "base/timer_rpl.h"
  30. #include "base/event_filter.h"
  31. #include "base/global_shortcuts.h"
  32. #include "base/platform/base_platform_info.h"
  33. #include "base/unixtime.h"
  34. #include "data/data_channel.h"
  35. #include "data/data_chat.h"
  36. #include "data/data_group_call.h"
  37. #include "data/data_user.h"
  38. #include "calls/group/calls_group_rtmp.h"
  39. #include "ui/toast/toast.h"
  40. #include "data/data_changes.h"
  41. #include "core/application.h"
  42. #include "core/core_settings.h"
  43. #include "webrtc/webrtc_audio_input_tester.h"
  44. #include "webrtc/webrtc_device_resolver.h"
  45. #include "settings/settings_calls.h"
  46. #include "settings/settings_credits_graphics.h"
  47. #include "main/main_session.h"
  48. #include "apiwrap.h"
  49. #include "api/api_invite_links.h"
  50. #include "styles/style_layers.h"
  51. #include "styles/style_calls.h"
  52. #include "styles/style_settings.h"
  53. #include <QtGui/QGuiApplication>
  54. namespace Calls::Group {
  55. namespace {
  56. constexpr auto kDelaysCount = 201;
  57. constexpr auto kMicrophoneTooltipAfterLoudCount = 3;
  58. constexpr auto kDropLoudAfterQuietCount = 5;
  59. constexpr auto kMicrophoneTooltipLevelThreshold = 0.2;
  60. constexpr auto kMicrophoneTooltipCheckInterval = crl::time(500);
  61. #ifdef Q_OS_MAC
  62. constexpr auto kCheckAccessibilityInterval = crl::time(500);
  63. #endif // Q_OS_MAC
  64. void SaveCallJoinMuted(
  65. not_null<PeerData*> peer,
  66. CallId callId,
  67. bool joinMuted) {
  68. const auto call = peer->groupCall();
  69. if (!call
  70. || call->id() != callId
  71. || !peer->canManageGroupCall()
  72. || !call->canChangeJoinMuted()
  73. || call->joinMuted() == joinMuted) {
  74. return;
  75. }
  76. call->setJoinMutedLocally(joinMuted);
  77. peer->session().api().request(MTPphone_ToggleGroupCallSettings(
  78. MTP_flags(MTPphone_ToggleGroupCallSettings::Flag::f_join_muted),
  79. call->input(),
  80. MTP_bool(joinMuted)
  81. )).send();
  82. }
  83. [[nodiscard]] crl::time DelayByIndex(int index) {
  84. return index * crl::time(10);
  85. }
  86. [[nodiscard]] QString FormatDelay(crl::time delay) {
  87. return (delay < crl::time(1000))
  88. ? tr::lng_group_call_ptt_delay_ms(
  89. tr::now,
  90. lt_amount,
  91. QString::number(delay))
  92. : tr::lng_group_call_ptt_delay_s(
  93. tr::now,
  94. lt_amount,
  95. QString::number(delay / 1000., 'f', 2));
  96. }
  97. object_ptr<ShareBox> ShareInviteLinkBox(
  98. not_null<PeerData*> peer,
  99. const QString &linkSpeaker,
  100. const QString &linkListener,
  101. std::shared_ptr<Ui::Show> show) {
  102. const auto sending = std::make_shared<bool>();
  103. const auto box = std::make_shared<QPointer<ShareBox>>();
  104. auto bottom = linkSpeaker.isEmpty()
  105. ? nullptr
  106. : object_ptr<Ui::PaddingWrap<Ui::Checkbox>>(
  107. nullptr,
  108. object_ptr<Ui::Checkbox>(
  109. nullptr,
  110. tr::lng_group_call_share_speaker(tr::now),
  111. true,
  112. st::groupCallCheckbox),
  113. st::groupCallShareMutedMargin);
  114. const auto speakerCheckbox = bottom ? bottom->entity() : nullptr;
  115. const auto currentLink = [=] {
  116. return (!speakerCheckbox || !speakerCheckbox->checked())
  117. ? linkListener
  118. : linkSpeaker;
  119. };
  120. auto copyCallback = [=] {
  121. QGuiApplication::clipboard()->setText(currentLink());
  122. show->showToast(tr::lng_group_invite_copied(tr::now));
  123. };
  124. auto countMessagesCallback = [=](const TextWithTags &comment) {
  125. return 1;
  126. };
  127. auto submitCallback = [=](
  128. std::vector<not_null<Data::Thread*>> &&result,
  129. Fn<bool()> checkPaid,
  130. TextWithTags &&comment,
  131. Api::SendOptions options,
  132. Data::ForwardOptions) {
  133. if (*sending || result.empty()) {
  134. return;
  135. }
  136. const auto error = GetErrorForSending(
  137. result,
  138. { .text = &comment });
  139. if (error.error) {
  140. if (const auto weak = *box) {
  141. weak->getDelegate()->show(
  142. MakeSendErrorBox(error, result.size() > 1));
  143. }
  144. return;
  145. } else if (!checkPaid()) {
  146. return;
  147. }
  148. *sending = true;
  149. const auto link = currentLink();
  150. if (!comment.text.isEmpty()) {
  151. comment.text = link + "\n" + comment.text;
  152. const auto add = link.size() + 1;
  153. for (auto &tag : comment.tags) {
  154. tag.offset += add;
  155. }
  156. } else {
  157. comment.text = link;
  158. }
  159. auto &api = peer->session().api();
  160. for (const auto thread : result) {
  161. auto message = Api::MessageToSend(
  162. Api::SendAction(thread, options));
  163. message.textWithTags = comment;
  164. message.action.clearDraft = false;
  165. api.sendMessage(std::move(message));
  166. }
  167. if (*box) {
  168. (*box)->closeBox();
  169. }
  170. show->showToast(tr::lng_share_done(tr::now));
  171. };
  172. auto filterCallback = [](not_null<Data::Thread*> thread) {
  173. if (const auto user = thread->peer()->asUser()) {
  174. if (user->canSendIgnoreMoneyRestrictions()) {
  175. return true;
  176. }
  177. }
  178. return Data::CanSend(thread, ChatRestriction::SendOther);
  179. };
  180. const auto st = ::Settings::DarkCreditsEntryBoxStyle();
  181. auto result = Box<ShareBox>(ShareBox::Descriptor{
  182. .session = &peer->session(),
  183. .copyCallback = std::move(copyCallback),
  184. .countMessagesCallback = std::move(countMessagesCallback),
  185. .submitCallback = std::move(submitCallback),
  186. .filterCallback = std::move(filterCallback),
  187. .bottomWidget = std::move(bottom),
  188. .copyLinkText = rpl::conditional(
  189. (speakerCheckbox
  190. ? speakerCheckbox->checkedValue()
  191. : rpl::single(false)),
  192. tr::lng_group_call_copy_speaker_link(),
  193. tr::lng_group_call_copy_listener_link()),
  194. .st = st.shareBox ? *st.shareBox : ShareBoxStyleOverrides(),
  195. .moneyRestrictionError = ShareMessageMoneyRestrictionError(),
  196. });
  197. *box = result.data();
  198. return result;
  199. }
  200. } // namespace
  201. void SettingsBox(
  202. not_null<Ui::GenericBox*> box,
  203. not_null<GroupCall*> call) {
  204. using namespace Settings;
  205. const auto weakCall = base::make_weak(call);
  206. const auto weakBox = Ui::MakeWeak(box);
  207. struct State {
  208. std::unique_ptr<Webrtc::DeviceResolver> deviceId;
  209. std::unique_ptr<Webrtc::AudioInputTester> micTester;
  210. Ui::LevelMeter *micTestLevel = nullptr;
  211. float micLevel = 0.;
  212. Ui::Animations::Simple micLevelAnimation;
  213. base::Timer levelUpdateTimer;
  214. bool generatingLink = false;
  215. };
  216. const auto peer = call->peer();
  217. const auto state = box->lifetime().make_state<State>();
  218. const auto real = peer->groupCall();
  219. const auto rtmp = call->rtmp();
  220. const auto id = call->id();
  221. const auto goodReal = (real && real->id() == id);
  222. const auto layout = box->verticalLayout();
  223. const auto &settings = Core::App().settings();
  224. const auto joinMuted = goodReal ? real->joinMuted() : false;
  225. const auto canChangeJoinMuted = !rtmp
  226. && goodReal
  227. && real->canChangeJoinMuted();
  228. const auto addCheck = (peer->canManageGroupCall() && canChangeJoinMuted);
  229. const auto addDivider = [&] {
  230. layout->add(object_ptr<Ui::BoxContentDivider>(
  231. layout,
  232. st::boxDividerHeight,
  233. st::groupCallDividerBg));
  234. };
  235. if (addCheck) {
  236. Ui::AddSkip(layout);
  237. }
  238. const auto muteJoined = addCheck
  239. ? layout->add(object_ptr<Ui::SettingsButton>(
  240. layout,
  241. tr::lng_group_call_new_muted(),
  242. st::groupCallSettingsButton))->toggleOn(rpl::single(joinMuted))
  243. : nullptr;
  244. if (addCheck) {
  245. Ui::AddSkip(layout);
  246. }
  247. auto playbackIdWithFallback = Webrtc::DeviceIdValueWithFallback(
  248. Core::App().settings().callPlaybackDeviceIdValue(),
  249. Core::App().settings().playbackDeviceIdValue());
  250. AddButtonWithLabel(
  251. layout,
  252. tr::lng_group_call_speakers(),
  253. PlaybackDeviceNameValue(rpl::duplicate(playbackIdWithFallback)),
  254. st::groupCallSettingsButton
  255. )->addClickHandler([=] {
  256. box->getDelegate()->show(ChoosePlaybackDeviceBox(
  257. rpl::duplicate(playbackIdWithFallback),
  258. crl::guard(box, [=](const QString &id) {
  259. Core::App().settings().setCallPlaybackDeviceId(id);
  260. Core::App().saveSettingsDelayed();
  261. }),
  262. &st::groupCallCheckbox,
  263. &st::groupCallRadio));
  264. });
  265. if (!rtmp) {
  266. auto captureIdWithFallback = Webrtc::DeviceIdValueWithFallback(
  267. Core::App().settings().callCaptureDeviceIdValue(),
  268. Core::App().settings().captureDeviceIdValue());
  269. AddButtonWithLabel(
  270. layout,
  271. tr::lng_group_call_microphone(),
  272. CaptureDeviceNameValue(rpl::duplicate(captureIdWithFallback)),
  273. st::groupCallSettingsButton
  274. )->addClickHandler([=] {
  275. box->getDelegate()->show(ChooseCaptureDeviceBox(
  276. rpl::duplicate(captureIdWithFallback),
  277. crl::guard(box, [=](const QString &id) {
  278. Core::App().settings().setCallCaptureDeviceId(id);
  279. Core::App().saveSettingsDelayed();
  280. }),
  281. &st::groupCallCheckbox,
  282. &st::groupCallRadio));
  283. });
  284. state->micTestLevel = box->addRow(
  285. object_ptr<Ui::LevelMeter>(
  286. box.get(),
  287. st::groupCallLevelMeter),
  288. st::settingsLevelMeterPadding);
  289. state->micTestLevel->resize(QSize(0, st::defaultLevelMeter.height));
  290. state->levelUpdateTimer.setCallback([=] {
  291. const auto was = state->micLevel;
  292. state->micLevel = state->micTester->getAndResetLevel();
  293. state->micLevelAnimation.start([=] {
  294. state->micTestLevel->setValue(
  295. state->micLevelAnimation.value(state->micLevel));
  296. }, was, state->micLevel, kMicTestAnimationDuration);
  297. });
  298. Ui::AddSkip(layout);
  299. //Ui::AddDivider(layout);
  300. //Ui::AddSkip(layout);
  301. layout->add(object_ptr<Ui::SettingsButton>(
  302. layout,
  303. tr::lng_group_call_noise_suppression(),
  304. st::groupCallSettingsButton
  305. ))->toggleOn(rpl::single(
  306. settings.groupCallNoiseSuppression()
  307. ))->toggledChanges(
  308. ) | rpl::start_with_next([=](bool enabled) {
  309. Core::App().settings().setGroupCallNoiseSuppression(enabled);
  310. call->setNoiseSuppression(enabled);
  311. Core::App().saveSettingsDelayed();
  312. }, layout->lifetime());
  313. using GlobalShortcut = base::GlobalShortcut;
  314. struct PushToTalkState {
  315. rpl::variable<QString> recordText = tr::lng_group_call_ptt_shortcut();
  316. rpl::variable<QString> shortcutText;
  317. rpl::event_stream<bool> pushToTalkToggles;
  318. std::shared_ptr<base::GlobalShortcutManager> manager;
  319. GlobalShortcut shortcut;
  320. crl::time delay = 0;
  321. bool recording = false;
  322. };
  323. if (base::GlobalShortcutsAvailable()) {
  324. const auto state = box->lifetime().make_state<PushToTalkState>();
  325. if (!base::GlobalShortcutsAllowed()) {
  326. Core::App().settings().setGroupCallPushToTalk(false);
  327. }
  328. const auto tryFillFromManager = [=] {
  329. state->shortcut = state->manager
  330. ? state->manager->shortcutFromSerialized(
  331. Core::App().settings().groupCallPushToTalkShortcut())
  332. : nullptr;
  333. state->shortcutText = state->shortcut
  334. ? state->shortcut->toDisplayString()
  335. : QString();
  336. };
  337. state->manager = settings.groupCallPushToTalk()
  338. ? call->ensureGlobalShortcutManager()
  339. : nullptr;
  340. tryFillFromManager();
  341. state->delay = settings.groupCallPushToTalkDelay();
  342. const auto pushToTalk = layout->add(
  343. object_ptr<Ui::SettingsButton>(
  344. layout,
  345. tr::lng_group_call_push_to_talk(),
  346. st::groupCallSettingsButton
  347. ))->toggleOn(rpl::single(
  348. settings.groupCallPushToTalk()
  349. ) | rpl::then(state->pushToTalkToggles.events()));
  350. const auto pushToTalkWrap = layout->add(
  351. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  352. layout,
  353. object_ptr<Ui::VerticalLayout>(layout)));
  354. const auto pushToTalkInner = pushToTalkWrap->entity();
  355. const auto recording = pushToTalkInner->add(
  356. object_ptr<Ui::SettingsButton>(
  357. pushToTalkInner,
  358. state->recordText.value(),
  359. st::groupCallSettingsButton));
  360. CreateRightLabel(
  361. recording,
  362. state->shortcutText.value(),
  363. st::groupCallSettingsButton,
  364. state->recordText.value());
  365. const auto applyAndSave = [=] {
  366. call->applyGlobalShortcutChanges();
  367. Core::App().saveSettingsDelayed();
  368. };
  369. const auto showPrivacyRequest = [=] {
  370. #ifdef Q_OS_MAC
  371. if (!Platform::IsMac10_14OrGreater()) {
  372. return;
  373. }
  374. const auto requestInputMonitoring = Platform::IsMac10_15OrGreater();
  375. box->getDelegate()->show(Box([=](not_null<Ui::GenericBox*> box) {
  376. box->addRow(
  377. object_ptr<Ui::FlatLabel>(
  378. box.get(),
  379. rpl::combine(
  380. tr::lng_group_call_mac_access(),
  381. (requestInputMonitoring
  382. ? tr::lng_group_call_mac_input()
  383. : tr::lng_group_call_mac_accessibility())
  384. ) | rpl::map([](QString a, QString b) {
  385. auto result = Ui::Text::RichLangValue(a);
  386. result.append("\n\n").append(Ui::Text::RichLangValue(b));
  387. return result;
  388. }),
  389. st::groupCallBoxLabel),
  390. style::margins(
  391. st::boxRowPadding.left(),
  392. st::boxPadding.top(),
  393. st::boxRowPadding.right(),
  394. st::boxPadding.bottom()));
  395. box->addButton(tr::lng_group_call_mac_settings(), [=] {
  396. if (requestInputMonitoring) {
  397. Platform::OpenInputMonitoringPrivacySettings();
  398. } else {
  399. Platform::OpenAccessibilityPrivacySettings();
  400. }
  401. });
  402. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  403. if (!requestInputMonitoring) {
  404. // Accessibility is enabled without app restart, so short-poll it.
  405. base::timer_each(
  406. kCheckAccessibilityInterval
  407. ) | rpl::filter([] {
  408. return base::GlobalShortcutsAllowed();
  409. }) | rpl::start_with_next([=] {
  410. box->closeBox();
  411. }, box->lifetime());
  412. }
  413. }));
  414. #endif // Q_OS_MAC
  415. };
  416. const auto ensureManager = [=] {
  417. if (state->manager) {
  418. return true;
  419. } else if (base::GlobalShortcutsAllowed()) {
  420. state->manager = call->ensureGlobalShortcutManager();
  421. tryFillFromManager();
  422. return true;
  423. }
  424. showPrivacyRequest();
  425. return false;
  426. };
  427. const auto stopRecording = [=] {
  428. state->recording = false;
  429. state->recordText = tr::lng_group_call_ptt_shortcut();
  430. state->shortcutText = state->shortcut
  431. ? state->shortcut->toDisplayString()
  432. : QString();
  433. recording->setColorOverride(std::nullopt);
  434. if (state->manager) {
  435. state->manager->stopRecording();
  436. }
  437. };
  438. const auto startRecording = [=] {
  439. if (!ensureManager()) {
  440. state->pushToTalkToggles.fire(false);
  441. pushToTalkWrap->hide(anim::type::instant);
  442. return;
  443. }
  444. state->recording = true;
  445. state->recordText = tr::lng_group_call_ptt_recording();
  446. recording->setColorOverride(
  447. st::groupCallSettingsAttentionButton.textFg->c);
  448. auto progress = crl::guard(box, [=](GlobalShortcut shortcut) {
  449. state->shortcutText = shortcut->toDisplayString();
  450. });
  451. auto done = crl::guard(box, [=](GlobalShortcut shortcut) {
  452. state->shortcut = shortcut;
  453. Core::App().settings().setGroupCallPushToTalkShortcut(shortcut
  454. ? shortcut->serialize()
  455. : QByteArray());
  456. applyAndSave();
  457. stopRecording();
  458. });
  459. state->manager->startRecording(std::move(progress), std::move(done));
  460. };
  461. recording->addClickHandler([=] {
  462. if (state->recording) {
  463. stopRecording();
  464. } else {
  465. startRecording();
  466. }
  467. });
  468. const auto label = pushToTalkInner->add(
  469. object_ptr<Ui::LabelSimple>(
  470. pushToTalkInner,
  471. st::groupCallDelayLabel),
  472. st::groupCallDelayLabelMargin);
  473. const auto value = std::clamp(
  474. state->delay,
  475. crl::time(0),
  476. DelayByIndex(kDelaysCount - 1));
  477. const auto callback = [=](crl::time delay) {
  478. state->delay = delay;
  479. label->setText(tr::lng_group_call_ptt_delay(
  480. tr::now,
  481. lt_delay,
  482. FormatDelay(delay)));
  483. if (Core::App().settings().groupCallPushToTalkDelay() != delay) {
  484. Core::App().settings().setGroupCallPushToTalkDelay(delay);
  485. applyAndSave();
  486. }
  487. };
  488. callback(value);
  489. const auto slider = pushToTalkInner->add(
  490. object_ptr<Ui::MediaSlider>(
  491. pushToTalkInner,
  492. st::groupCallDelaySlider),
  493. st::groupCallDelayMargin);
  494. slider->resize(st::groupCallDelaySlider.seekSize);
  495. slider->setPseudoDiscrete(
  496. kDelaysCount,
  497. DelayByIndex,
  498. value,
  499. callback);
  500. pushToTalkWrap->toggle(
  501. settings.groupCallPushToTalk(),
  502. anim::type::instant);
  503. pushToTalk->toggledChanges(
  504. ) | rpl::start_with_next([=](bool toggled) {
  505. if (!toggled) {
  506. stopRecording();
  507. } else if (!ensureManager()) {
  508. state->pushToTalkToggles.fire(false);
  509. pushToTalkWrap->hide(anim::type::instant);
  510. return;
  511. }
  512. Core::App().settings().setGroupCallPushToTalk(toggled);
  513. applyAndSave();
  514. pushToTalkWrap->toggle(toggled, anim::type::normal);
  515. }, pushToTalk->lifetime());
  516. auto boxKeyFilter = [=](not_null<QEvent*> e) {
  517. return (e->type() == QEvent::KeyPress && state->recording)
  518. ? base::EventFilterResult::Cancel
  519. : base::EventFilterResult::Continue;
  520. };
  521. box->lifetime().make_state<base::unique_qptr<QObject>>(
  522. base::install_event_filter(box, std::move(boxKeyFilter)));
  523. }
  524. Ui::AddSkip(layout);
  525. //Ui::AddDivider(layout);
  526. //Ui::AddSkip(layout);
  527. }
  528. auto shareLink = Fn<void()>();
  529. if (peer->isChannel()
  530. && peer->asChannel()->hasUsername()
  531. && goodReal) {
  532. const auto showBox = crl::guard(box, [=](
  533. object_ptr<Ui::BoxContent> next) {
  534. box->getDelegate()->show(std::move(next));
  535. });
  536. const auto showToast = crl::guard(box, [=](QString text) {
  537. box->showToast(text);
  538. });
  539. auto [shareLinkCallback, shareLinkLifetime] = ShareInviteLinkAction(
  540. peer,
  541. box->uiShow());
  542. shareLink = std::move(shareLinkCallback);
  543. box->lifetime().add(std::move(shareLinkLifetime));
  544. } else {
  545. const auto lookupLink = [=] {
  546. if (const auto group = peer->asMegagroup()) {
  547. return group->hasUsername()
  548. ? group->session().createInternalLinkFull(
  549. group->username())
  550. : group->inviteLink();
  551. } else if (const auto chat = peer->asChat()) {
  552. return chat->inviteLink();
  553. }
  554. return QString();
  555. };
  556. const auto canCreateLink = [&] {
  557. if (const auto chat = peer->asChat()) {
  558. return chat->canHaveInviteLink();
  559. } else if (const auto group = peer->asMegagroup()) {
  560. return group->canHaveInviteLink();
  561. }
  562. return false;
  563. };
  564. const auto alreadyHasLink = !lookupLink().isEmpty();
  565. if (alreadyHasLink || canCreateLink()) {
  566. if (!alreadyHasLink) {
  567. // Request invite link.
  568. peer->session().api().requestFullPeer(peer);
  569. }
  570. const auto copyLink = [=] {
  571. const auto link = lookupLink();
  572. if (link.isEmpty()) {
  573. return false;
  574. }
  575. QGuiApplication::clipboard()->setText(link);
  576. if (weakBox) {
  577. box->showToast(
  578. tr::lng_create_channel_link_copied(tr::now));
  579. }
  580. return true;
  581. };
  582. shareLink = [=] {
  583. if (!copyLink() && !state->generatingLink) {
  584. state->generatingLink = true;
  585. peer->session().api().inviteLinks().create({
  586. peer,
  587. crl::guard(layout, [=](auto&&) { copyLink(); })
  588. });
  589. }
  590. };
  591. }
  592. }
  593. if (shareLink) {
  594. layout->add(object_ptr<Ui::SettingsButton>(
  595. layout,
  596. tr::lng_group_call_share(),
  597. st::groupCallSettingsButton
  598. ))->addClickHandler(std::move(shareLink));
  599. }
  600. if (rtmp && !call->rtmpInfo().url.isEmpty()) {
  601. Ui::AddSkip(layout);
  602. addDivider();
  603. Ui::AddSkip(layout);
  604. struct State {
  605. base::unique_qptr<Ui::PopupMenu> menu;
  606. mtpRequestId requestId;
  607. rpl::event_stream<RtmpInfo> data;
  608. };
  609. const auto top = box->addTopButton(st::groupCallMenuToggle);
  610. const auto state = top->lifetime().make_state<State>();
  611. const auto revokeSure = [=] {
  612. const auto session = &peer->session();
  613. state->requestId = session->api().request(
  614. MTPphone_GetGroupCallStreamRtmpUrl(
  615. peer->input,
  616. MTP_bool(true)
  617. )).done([=](const MTPphone_GroupCallStreamRtmpUrl &result) {
  618. auto data = result.match([&](
  619. const MTPDphone_groupCallStreamRtmpUrl &data) {
  620. return RtmpInfo{
  621. .url = qs(data.vurl()),
  622. .key = qs(data.vkey()),
  623. };
  624. });
  625. if (const auto call = weakCall.get()) {
  626. call->setRtmpInfo(data);
  627. }
  628. if (!top) {
  629. return;
  630. }
  631. state->requestId = 0;
  632. state->data.fire(std::move(data));
  633. }).fail([=] {
  634. state->requestId = 0;
  635. }).send();
  636. };
  637. const auto revoke = [=] {
  638. if (state->requestId || !top) {
  639. return;
  640. }
  641. box->getDelegate()->show(Ui::MakeConfirmBox({
  642. .text = tr::lng_group_call_rtmp_revoke_sure(),
  643. .confirmed = [=](Fn<void()> &&close) {
  644. revokeSure();
  645. close();
  646. },
  647. .confirmText = tr::lng_group_invite_context_revoke(),
  648. .labelStyle = &st::groupCallBoxLabel,
  649. }));
  650. };
  651. top->setClickedCallback([=] {
  652. state->menu = base::make_unique_q<Ui::PopupMenu>(
  653. box,
  654. st::groupCallPopupMenu);
  655. state->menu->addAction(
  656. tr::lng_group_call_rtmp_revoke(tr::now),
  657. revoke);
  658. state->menu->setForcedOrigin(
  659. Ui::PanelAnimation::Origin::TopRight);
  660. top->setForceRippled(true);
  661. const auto raw = state->menu.get();
  662. raw->setDestroyedCallback([=] {
  663. if ((state->menu == raw) && top) {
  664. top->setForceRippled(false);
  665. }
  666. });
  667. state->menu->popup(
  668. top->mapToGlobal(QPoint(top->width() / 2, top->height())));
  669. return true;
  670. });
  671. StartRtmpProcess::FillRtmpRows(
  672. layout,
  673. false,
  674. box->uiShow(),
  675. state->data.events(),
  676. &st::groupCallBoxLabel,
  677. &st::groupCallSettingsRtmpShowButton,
  678. &st::groupCallSubsectionTitle,
  679. &st::groupCallAttentionBoxButton,
  680. &st::groupCallPopupMenu);
  681. state->data.fire(call->rtmpInfo());
  682. addDivider();
  683. Ui::AddSkip(layout);
  684. }
  685. if (rtmp) {
  686. const auto volumeItem = layout->add(
  687. object_ptr<MenuVolumeItem>(
  688. layout,
  689. st::groupCallVolumeSettings,
  690. st::groupCallVolumeSettingsSlider,
  691. call->otherParticipantStateValue(
  692. ) | rpl::filter([=](const Group::ParticipantState &data) {
  693. return data.peer == peer;
  694. }),
  695. call->rtmpVolume(),
  696. Group::kMaxVolume,
  697. false,
  698. st::groupCallVolumeSettingsPadding));
  699. const auto toggleMute = crl::guard(layout, [=](bool m, bool local) {
  700. if (call) {
  701. call->toggleMute({
  702. .peer = peer,
  703. .mute = m,
  704. .locallyOnly = local,
  705. });
  706. }
  707. });
  708. const auto changeVolume = crl::guard(layout, [=](int v, bool local) {
  709. if (call) {
  710. call->changeVolume({
  711. .peer = peer,
  712. .volume = std::clamp(v, 1, Group::kMaxVolume),
  713. .locallyOnly = local,
  714. });
  715. }
  716. });
  717. volumeItem->toggleMuteLocallyRequests(
  718. ) | rpl::start_with_next([=](bool muted) {
  719. toggleMute(muted, true);
  720. }, volumeItem->lifetime());
  721. volumeItem->changeVolumeLocallyRequests(
  722. ) | rpl::start_with_next([=](int volume) {
  723. changeVolume(volume, true);
  724. }, volumeItem->lifetime());
  725. }
  726. if (peer->canManageGroupCall()) {
  727. layout->add(object_ptr<Ui::SettingsButton>(
  728. layout,
  729. (peer->isBroadcast()
  730. ? tr::lng_group_call_end_channel()
  731. : tr::lng_group_call_end()),
  732. st::groupCallSettingsAttentionButton
  733. ))->addClickHandler([=] {
  734. if (const auto call = weakCall.get()) {
  735. box->getDelegate()->show(Box(
  736. LeaveBox,
  737. call,
  738. true,
  739. BoxContext::GroupCallPanel));
  740. box->closeBox();
  741. }
  742. });
  743. }
  744. if (!rtmp) {
  745. box->setShowFinishedCallback([=] {
  746. // Means we finished showing the box.
  747. crl::on_main(box, [=] {
  748. state->deviceId = std::make_unique<Webrtc::DeviceResolver>(
  749. &Core::App().mediaDevices(),
  750. Webrtc::DeviceType::Capture,
  751. Webrtc::DeviceIdValueWithFallback(
  752. Core::App().settings().callCaptureDeviceIdValue(),
  753. Core::App().settings().captureDeviceIdValue()));
  754. state->micTester = std::make_unique<Webrtc::AudioInputTester>(
  755. state->deviceId->value());
  756. state->levelUpdateTimer.callEach(kMicTestUpdateInterval);
  757. });
  758. });
  759. }
  760. box->setTitle(tr::lng_group_call_settings_title());
  761. box->boxClosing(
  762. ) | rpl::start_with_next([=] {
  763. if (canChangeJoinMuted
  764. && muteJoined
  765. && muteJoined->toggled() != joinMuted) {
  766. SaveCallJoinMuted(peer, id, muteJoined->toggled());
  767. }
  768. }, box->lifetime());
  769. box->addButton(tr::lng_box_done(), [=] {
  770. box->closeBox();
  771. });
  772. }
  773. std::pair<Fn<void()>, rpl::lifetime> ShareInviteLinkAction(
  774. not_null<PeerData*> peer,
  775. std::shared_ptr<Ui::Show> show) {
  776. auto lifetime = rpl::lifetime();
  777. struct State {
  778. State(not_null<Main::Session*> session) : session(session) {
  779. }
  780. ~State() {
  781. session->api().request(linkListenerRequestId).cancel();
  782. session->api().request(linkSpeakerRequestId).cancel();
  783. }
  784. not_null<Main::Session*> session;
  785. std::optional<QString> linkSpeaker;
  786. QString linkListener;
  787. mtpRequestId linkListenerRequestId = 0;
  788. mtpRequestId linkSpeakerRequestId = 0;
  789. bool generatingLink = false;
  790. };
  791. const auto state = lifetime.make_state<State>(&peer->session());
  792. if (!peer->canManageGroupCall()) {
  793. state->linkSpeaker = QString();
  794. }
  795. const auto shareReady = [=] {
  796. if (!state->linkSpeaker.has_value()
  797. || state->linkListener.isEmpty()) {
  798. return false;
  799. }
  800. show->showBox(ShareInviteLinkBox(
  801. peer,
  802. *state->linkSpeaker,
  803. state->linkListener,
  804. show));
  805. return true;
  806. };
  807. auto callback = [=] {
  808. const auto real = peer->migrateToOrMe()->groupCall();
  809. if (shareReady() || state->generatingLink || !real) {
  810. return;
  811. }
  812. state->generatingLink = true;
  813. state->linkListenerRequestId = peer->session().api().request(
  814. MTPphone_ExportGroupCallInvite(
  815. MTP_flags(0),
  816. real->input()
  817. )
  818. ).done([=](const MTPphone_ExportedGroupCallInvite &result) {
  819. state->linkListenerRequestId = 0;
  820. result.match([&](
  821. const MTPDphone_exportedGroupCallInvite &data) {
  822. state->linkListener = qs(data.vlink());
  823. shareReady();
  824. });
  825. }).send();
  826. if (real->rtmp()) {
  827. state->linkSpeaker = QString();
  828. state->linkSpeakerRequestId = 0;
  829. shareReady();
  830. } else if (!state->linkSpeaker.has_value()) {
  831. using Flag = MTPphone_ExportGroupCallInvite::Flag;
  832. state->linkSpeakerRequestId = peer->session().api().request(
  833. MTPphone_ExportGroupCallInvite(
  834. MTP_flags(Flag::f_can_self_unmute),
  835. real->input())
  836. ).done([=](const MTPphone_ExportedGroupCallInvite &result) {
  837. state->linkSpeakerRequestId = 0;
  838. result.match([&](
  839. const MTPDphone_exportedGroupCallInvite &data) {
  840. state->linkSpeaker = qs(data.vlink());
  841. shareReady();
  842. });
  843. }).fail([=] {
  844. state->linkSpeakerRequestId = 0;
  845. state->linkSpeaker = QString();
  846. shareReady();
  847. }).send();
  848. }
  849. };
  850. return { std::move(callback), std::move(lifetime) };
  851. }
  852. MicLevelTester::MicLevelTester(Fn<void()> show)
  853. : _show(std::move(show))
  854. , _timer([=] { check(); })
  855. , _deviceId(std::make_unique<Webrtc::DeviceResolver>(
  856. &Core::App().mediaDevices(),
  857. Webrtc::DeviceType::Capture,
  858. Webrtc::DeviceIdValueWithFallback(
  859. Core::App().settings().callCaptureDeviceIdValue(),
  860. Core::App().settings().captureDeviceIdValue())))
  861. , _tester(std::make_unique<Webrtc::AudioInputTester>(_deviceId->value())) {
  862. _timer.callEach(kMicrophoneTooltipCheckInterval);
  863. }
  864. bool MicLevelTester::showTooltip() const {
  865. return (_loudCount >= kMicrophoneTooltipAfterLoudCount);
  866. }
  867. void MicLevelTester::check() {
  868. const auto level = _tester->getAndResetLevel();
  869. if (level >= kMicrophoneTooltipLevelThreshold) {
  870. _quietCount = 0;
  871. if (++_loudCount >= kMicrophoneTooltipAfterLoudCount) {
  872. _show();
  873. }
  874. } else if (_loudCount > 0 && ++_quietCount >= kDropLoudAfterQuietCount) {
  875. _quietCount = 0;
  876. _loudCount = 0;
  877. }
  878. }
  879. } // namespace Calls::Group