/* 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 "settings/settings_calls.h" #include "settings/settings_common.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/labels.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/level_meter.h" #include "ui/widgets/buttons.h" #include "boxes/single_choice_box.h" #include "boxes/confirm_box.h" #include "platform/platform_specific.h" #include "main/main_session.h" #include "lang/lang_keys.h" #include "layout.h" #include "styles/style_settings.h" #include "ui/widgets/continuous_sliders.h" #include "window/window_session_controller.h" #include "core/application.h" #include "core/core_settings.h" #include "calls/calls_instance.h" #include "facades.h" #ifdef slots #undef slots #define NEED_TO_RESTORE_SLOTS #endif // slots #include #ifdef NEED_TO_RESTORE_SLOTS #define slots Q_SLOTS #undef NEED_TO_RESTORE_SLOTS #endif // NEED_TO_RESTORE_SLOTS namespace Settings { Calls::Calls( QWidget *parent, not_null controller) : Section(parent) , _controller(controller) { setupContent(); } Calls::~Calls() { if (_needWriteSettings) { Core::App().saveSettingsDelayed(); } } void Calls::sectionSaveChanges(FnMut done) { if (_micTester) { _micTester.reset(); } done(); } void Calls::setupContent() { using VoIP = tgvoip::VoIPController; const auto content = Ui::CreateChild(this); const auto getId = [](const auto &device) { return QString::fromStdString(device.id); }; const auto getName = [](const auto &device) { return QString::fromStdString(device.displayName); }; const auto &settings = Core::App().settings(); const auto currentOutputName = [&] { if (settings.callOutputDeviceID() == qsl("default")) { return tr::lng_settings_call_device_default(tr::now); } const auto &list = VoIP::EnumerateAudioOutputs(); const auto i = ranges::find( list, settings.callOutputDeviceID(), getId); return (i != end(list)) ? getName(*i) : settings.callOutputDeviceID(); }(); const auto currentInputName = [&] { if (settings.callInputDeviceID() == qsl("default")) { return tr::lng_settings_call_device_default(tr::now); } const auto &list = VoIP::EnumerateAudioInputs(); const auto i = ranges::find( list, settings.callInputDeviceID(), getId); return (i != end(list)) ? getName(*i) : settings.callInputDeviceID(); }(); AddSkip(content); AddSubsectionTitle(content, tr::lng_settings_call_section_output()); AddButtonWithLabel( content, tr::lng_settings_call_output_device(), rpl::single( currentOutputName ) | rpl::then( _outputNameStream.events() ), st::settingsButton )->addClickHandler([=] { const auto &devices = VoIP::EnumerateAudioOutputs(); const auto options = ranges::view::concat( ranges::view::single(tr::lng_settings_call_device_default(tr::now)), devices | ranges::view::transform(getName) ) | ranges::to_vector; const auto i = ranges::find( devices, Core::App().settings().callOutputDeviceID(), getId); const auto currentOption = (i != end(devices)) ? int(i - begin(devices) + 1) : 0; const auto save = crl::guard(this, [=](int option) { _outputNameStream.fire_copy(options[option]); const auto deviceId = option ? devices[option - 1].id : "default"; Core::App().settings().setCallOutputDeviceID( QString::fromStdString(deviceId)); Core::App().saveSettingsDelayed(); if (const auto call = Core::App().calls().currentCall()) { call->setCurrentAudioDevice(false, deviceId); } }); Ui::show(Box( tr::lng_settings_call_output_device(), options, currentOption, save)); }); const auto outputLabel = content->add( object_ptr( content, st::settingsAudioVolumeLabel), st::settingsAudioVolumeLabelPadding); const auto outputSlider = content->add( object_ptr( content, st::settingsAudioVolumeSlider), st::settingsAudioVolumeSliderPadding); const auto updateOutputLabel = [=](int value) { const auto percent = QString::number(value); outputLabel->setText( tr::lng_settings_call_output_volume(tr::now, lt_percent, percent)); }; const auto updateOutputVolume = [=](int value) { _needWriteSettings = true; updateOutputLabel(value); Core::App().settings().setCallOutputVolume(value); if (const auto call = Core::App().calls().currentCall()) { call->setAudioVolume(false, value / 100.0f); } }; outputSlider->resize(st::settingsAudioVolumeSlider.seekSize); outputSlider->setPseudoDiscrete( 201, [](int val) { return val; }, settings.callOutputVolume(), updateOutputVolume); updateOutputLabel(Core::App().settings().callOutputVolume()); AddSkip(content); AddDivider(content); AddSkip(content); AddSubsectionTitle(content, tr::lng_settings_call_section_input()); AddButtonWithLabel( content, tr::lng_settings_call_input_device(), rpl::single( currentInputName ) | rpl::then( _inputNameStream.events() ), st::settingsButton )->addClickHandler([=] { const auto &devices = VoIP::EnumerateAudioInputs(); const auto options = ranges::view::concat( ranges::view::single(tr::lng_settings_call_device_default(tr::now)), devices | ranges::view::transform(getName) ) | ranges::to_vector; const auto i = ranges::find( devices, Core::App().settings().callInputDeviceID(), getId); const auto currentOption = (i != end(devices)) ? int(i - begin(devices) + 1) : 0; const auto save = crl::guard(this, [=](int option) { _inputNameStream.fire_copy(options[option]); const auto deviceId = option ? devices[option - 1].id : "default"; Core::App().settings().setCallInputDeviceID( QString::fromStdString(deviceId)); Core::App().saveSettingsDelayed(); if (_micTester) { stopTestingMicrophone(); } if (const auto call = Core::App().calls().currentCall()) { call->setCurrentAudioDevice(true, deviceId); } }); Ui::show(Box( tr::lng_settings_call_input_device(), options, currentOption, save)); }); const auto inputLabel = content->add( object_ptr( content, st::settingsAudioVolumeLabel), st::settingsAudioVolumeLabelPadding); const auto inputSlider = content->add( object_ptr( content, st::settingsAudioVolumeSlider), st::settingsAudioVolumeSliderPadding); const auto updateInputLabel = [=](int value) { const auto percent = QString::number(value); inputLabel->setText( tr::lng_settings_call_input_volume(tr::now, lt_percent, percent)); }; const auto updateInputVolume = [=](int value) { _needWriteSettings = true; updateInputLabel(value); Core::App().settings().setCallInputVolume(value); if (const auto call = Core::App().calls().currentCall()) { call->setAudioVolume(true, value / 100.0f); } }; inputSlider->resize(st::settingsAudioVolumeSlider.seekSize); inputSlider->setPseudoDiscrete(101, [](int val) { return val; }, settings.callInputVolume(), updateInputVolume); updateInputLabel(settings.callInputVolume()); AddButton( content, rpl::single( tr::lng_settings_call_test_mic(tr::now) ) | rpl::then( _micTestTextStream.events() ), st::settingsButton )->addClickHandler([=] { if (!_micTester) { requestPermissionAndStartTestingMicrophone(); } else { stopTestingMicrophone(); } }); _micTestLevel = content->add( object_ptr( content, st::defaultLevelMeter), st::settingsLevelMeterPadding); _micTestLevel->resize(QSize(0, st::defaultLevelMeter.height)); _levelUpdateTimer.setCallback([=] { _micTestLevel->setValue(_micTester->GetAndResetLevel()); }); AddSkip(content); AddDivider(content); AddSkip(content); AddSubsectionTitle(content, tr::lng_settings_call_section_other()); #if defined Q_OS_MAC && !defined OS_MAC_STORE AddButton( content, tr::lng_settings_call_audio_ducking(), st::settingsButton )->toggleOn( rpl::single(settings.callAudioDuckingEnabled()) )->toggledValue() | rpl::filter([](bool enabled) { return (enabled != Core::App().settings().callAudioDuckingEnabled()); }) | rpl::start_with_next([=](bool enabled) { Core::App().settings().setCallAudioDuckingEnabled(enabled); Core::App().saveSettingsDelayed(); if (const auto call = Core::App().calls().currentCall()) { call->setAudioDuckingEnabled(enabled); } }, content->lifetime()); #endif // Q_OS_MAC && !OS_MAC_STORE AddButton( content, tr::lng_settings_call_open_system_prefs(), st::settingsButton )->addClickHandler([] { const auto opened = Platform::OpenSystemSettings( Platform::SystemSettingsType::Audio); if (!opened) { Ui::show(Box(tr::lng_linux_no_audio_prefs(tr::now))); } }); AddSkip(content); Ui::ResizeFitChild(this, content); } void Calls::requestPermissionAndStartTestingMicrophone() { const auto status = Platform::GetPermissionStatus( Platform::PermissionType::Microphone); if (status == Platform::PermissionStatus::Granted) { startTestingMicrophone(); } else if (status == Platform::PermissionStatus::CanRequest) { const auto startTestingChecked = crl::guard(this, [=]( Platform::PermissionStatus status) { if (status == Platform::PermissionStatus::Granted) { crl::on_main(crl::guard(this, [=] { startTestingMicrophone(); })); } }); Platform::RequestPermission( Platform::PermissionType::Microphone, startTestingChecked); } else { const auto showSystemSettings = [] { Platform::OpenSystemSettingsForPermission( Platform::PermissionType::Microphone); Ui::hideLayer(); }; Ui::show(Box( tr::lng_no_mic_permission(tr::now), tr::lng_menu_settings(tr::now), showSystemSettings)); } } void Calls::startTestingMicrophone() { _micTestTextStream.fire(tr::lng_settings_call_stop_mic_test(tr::now)); _levelUpdateTimer.callEach(50); _micTester = std::make_unique( Core::App().settings().callInputDeviceID().toStdString()); if (_micTester->Failed()) { stopTestingMicrophone(); Ui::show(Box(tr::lng_call_error_audio_io(tr::now))); } } void Calls::stopTestingMicrophone() { _micTestTextStream.fire(tr::lng_settings_call_test_mic(tr::now)); _levelUpdateTimer.cancel(); _micTester.reset(); _micTestLevel->setValue(0.0f); } } // namespace Settings