/* 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 "calls/calls_group_settings.h" #include "calls/calls_group_call.h" #include "calls/calls_group_menu.h" // LeaveBox. #include "calls/calls_group_common.h" #include "calls/calls_instance.h" #include "calls/calls_choose_join_as.h" #include "ui/widgets/level_meter.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/input_fields.h" #include "ui/wrap/slide_wrap.h" #include "ui/text/text_utilities.h" #include "ui/toasts/common_toasts.h" #include "lang/lang_keys.h" #include "boxes/share_box.h" #include "history/history_message.h" // GetErrorTextForSending. #include "data/data_histories.h" #include "data/data_session.h" #include "base/timer_rpl.h" #include "base/event_filter.h" #include "base/global_shortcuts.h" #include "base/platform/base_platform_info.h" #include "base/unixtime.h" #include "data/data_channel.h" #include "data/data_chat.h" #include "data/data_group_call.h" #include "data/data_changes.h" #include "core/application.h" #include "boxes/single_choice_box.h" #include "webrtc/webrtc_audio_input_tester.h" #include "webrtc/webrtc_media_devices.h" #include "settings/settings_common.h" #include "settings/settings_calls.h" #include "main/main_session.h" #include "apiwrap.h" #include "api/api_invite_links.h" #include "styles/style_layers.h" #include "styles/style_calls.h" #include "styles/style_settings.h" #include namespace Calls::Group { namespace { constexpr auto kDelaysCount = 201; constexpr auto kCheckAccessibilityInterval = crl::time(500); void SaveCallJoinMuted( not_null peer, uint64 callId, bool joinMuted) { const auto call = peer->groupCall(); if (!call || call->id() != callId || !peer->canManageGroupCall() || !call->canChangeJoinMuted() || call->joinMuted() == joinMuted) { return; } call->setJoinMutedLocally(joinMuted); peer->session().api().request(MTPphone_ToggleGroupCallSettings( MTP_flags(MTPphone_ToggleGroupCallSettings::Flag::f_join_muted), call->input(), MTP_bool(joinMuted) )).send(); } [[nodiscard]] crl::time DelayByIndex(int index) { return index * crl::time(10); } [[nodiscard]] QString FormatDelay(crl::time delay) { return (delay < crl::time(1000)) ? tr::lng_group_call_ptt_delay_ms( tr::now, lt_amount, QString::number(delay)) : tr::lng_group_call_ptt_delay_s( tr::now, lt_amount, QString::number(delay / 1000., 'f', 2)); } object_ptr ShareInviteLinkBox( not_null peer, const QString &linkSpeaker, const QString &linkListener, Fn showToast) { const auto session = &peer->session(); const auto sending = std::make_shared(); const auto box = std::make_shared>(); auto bottom = linkSpeaker.isEmpty() ? nullptr : object_ptr>( nullptr, object_ptr( nullptr, tr::lng_group_call_share_speaker(tr::now), true, st::groupCallCheckbox), st::groupCallShareMutedMargin); const auto speakerCheckbox = bottom ? bottom->entity() : nullptr; const auto currentLink = [=] { return (!speakerCheckbox || !speakerCheckbox->checked()) ? linkListener : linkSpeaker; }; auto copyCallback = [=] { QGuiApplication::clipboard()->setText(currentLink()); showToast(tr::lng_group_invite_copied(tr::now)); }; auto submitCallback = [=]( std::vector> &&result, TextWithTags &&comment, Api::SendOptions options) { if (*sending || result.empty()) { return; } const auto error = [&] { for (const auto peer : result) { const auto error = GetErrorTextForSending( peer, {}, comment); if (!error.isEmpty()) { return std::make_pair(error, peer); } } return std::make_pair(QString(), result.front()); }(); if (!error.first.isEmpty()) { auto text = TextWithEntities(); if (result.size() > 1) { text.append( Ui::Text::Bold(error.second->name) ).append("\n\n"); } text.append(error.first); if (const auto weak = *box) { weak->getDelegate()->show( Box(ConfirmBox, text, nullptr, nullptr)); } return; } *sending = true; const auto link = currentLink(); if (!comment.text.isEmpty()) { comment.text = link + "\n" + comment.text; const auto add = link.size() + 1; for (auto &tag : comment.tags) { tag.offset += add; } } else { comment.text = link; } const auto owner = &peer->owner(); auto &api = peer->session().api(); auto &histories = owner->histories(); const auto requestType = Data::Histories::RequestType::Send; for (const auto peer : result) { const auto history = owner->history(peer); auto message = ApiWrap::MessageToSend(history); message.textWithTags = comment; message.action.options = options; message.action.clearDraft = false; api.sendMessage(std::move(message)); } if (*box) { (*box)->closeBox(); } showToast(tr::lng_share_done(tr::now)); }; auto filterCallback = [](PeerData *peer) { return peer->canWrite(); }; auto result = Box(ShareBox::Descriptor{ .session = &peer->session(), .copyCallback = std::move(copyCallback), .submitCallback = std::move(submitCallback), .filterCallback = std::move(filterCallback), .bottomWidget = std::move(bottom), .copyLinkText = rpl::conditional( (speakerCheckbox ? speakerCheckbox->checkedValue() : rpl::single(false)), tr::lng_group_call_copy_speaker_link(), tr::lng_group_call_copy_listener_link()), .stMultiSelect = &st::groupCallMultiSelect, .stComment = &st::groupCallShareBoxComment, .st = &st::groupCallShareBoxList }); *box = result.data(); return result; } } // namespace void SettingsBox( not_null box, not_null call) { using namespace Settings; const auto weakCall = base::make_weak(call.get()); const auto weakBox = Ui::MakeWeak(box); struct State { rpl::event_stream outputNameStream; rpl::event_stream inputNameStream; std::unique_ptr micTester; Ui::LevelMeter *micTestLevel = nullptr; float micLevel = 0.; Ui::Animations::Simple micLevelAnimation; base::Timer levelUpdateTimer; bool generatingLink = false; }; const auto peer = call->peer(); const auto state = box->lifetime().make_state(); const auto real = peer->groupCall(); const auto id = call->id(); const auto goodReal = (real && real->id() == id); const auto layout = box->verticalLayout(); const auto &settings = Core::App().settings(); const auto joinMuted = goodReal ? real->joinMuted() : false; const auto canChangeJoinMuted = (goodReal && real->canChangeJoinMuted()); const auto addCheck = (peer->canManageGroupCall() && canChangeJoinMuted); if (addCheck) { AddSkip(layout); } const auto muteJoined = addCheck ? AddButton( layout, tr::lng_group_call_new_muted(), st::groupCallSettingsButton)->toggleOn(rpl::single(joinMuted)) : nullptr; if (addCheck) { AddSkip(layout); } AddButtonWithLabel( layout, tr::lng_group_call_speakers(), rpl::single( CurrentAudioOutputName() ) | rpl::then( state->outputNameStream.events() ), st::groupCallSettingsButton )->addClickHandler([=] { box->getDelegate()->show(ChooseAudioOutputBox(crl::guard(box, [=]( const QString &id, const QString &name) { state->outputNameStream.fire_copy(name); }), &st::groupCallCheckbox, &st::groupCallRadio)); }); AddButtonWithLabel( layout, tr::lng_group_call_microphone(), rpl::single( CurrentAudioInputName() ) | rpl::then( state->inputNameStream.events() ), st::groupCallSettingsButton )->addClickHandler([=] { box->getDelegate()->show(ChooseAudioInputBox(crl::guard(box, [=]( const QString &id, const QString &name) { state->inputNameStream.fire_copy(name); if (state->micTester) { state->micTester->setDeviceId(id); } }), &st::groupCallCheckbox, &st::groupCallRadio)); }); state->micTestLevel = box->addRow( object_ptr( box.get(), st::groupCallLevelMeter), st::settingsLevelMeterPadding); state->micTestLevel->resize(QSize(0, st::defaultLevelMeter.height)); state->levelUpdateTimer.setCallback([=] { const auto was = state->micLevel; state->micLevel = state->micTester->getAndResetLevel(); state->micLevelAnimation.start([=] { state->micTestLevel->setValue( state->micLevelAnimation.value(state->micLevel)); }, was, state->micLevel, kMicTestAnimationDuration); }); AddSkip(layout); //AddDivider(layout); //AddSkip(layout); using GlobalShortcut = base::GlobalShortcut; struct PushToTalkState { rpl::variable recordText = tr::lng_group_call_ptt_shortcut(); rpl::variable shortcutText; rpl::event_stream pushToTalkToggles; std::shared_ptr manager; GlobalShortcut shortcut; crl::time delay = 0; bool recording = false; }; if (base::GlobalShortcutsAvailable()) { const auto state = box->lifetime().make_state(); if (!base::GlobalShortcutsAllowed()) { Core::App().settings().setGroupCallPushToTalk(false); } const auto tryFillFromManager = [=] { state->shortcut = state->manager ? state->manager->shortcutFromSerialized( Core::App().settings().groupCallPushToTalkShortcut()) : nullptr; state->shortcutText = state->shortcut ? state->shortcut->toDisplayString() : QString(); }; state->manager = settings.groupCallPushToTalk() ? call->ensureGlobalShortcutManager() : nullptr; tryFillFromManager(); state->delay = settings.groupCallPushToTalkDelay(); const auto pushToTalk = AddButton( layout, tr::lng_group_call_push_to_talk(), st::groupCallSettingsButton )->toggleOn(rpl::single( settings.groupCallPushToTalk() ) | rpl::then(state->pushToTalkToggles.events())); const auto pushToTalkWrap = layout->add( object_ptr>( layout, object_ptr(layout))); const auto pushToTalkInner = pushToTalkWrap->entity(); const auto recording = pushToTalkInner->add( object_ptr