From 11b991cddc13045b4e63e3ece16f938b2c2ca814 Mon Sep 17 00:00:00 2001 From: Gregory K Date: Sat, 5 Jan 2019 14:08:02 +0300 Subject: [PATCH] Add call settings (#5540) --- Telegram/Resources/langs/lang.strings | 16 + .../SourceFiles/boxes/single_choice_box.cpp | 39 +++ .../SourceFiles/boxes/single_choice_box.h | 35 ++ Telegram/SourceFiles/calls/calls_call.cpp | 38 ++- Telegram/SourceFiles/calls/calls_call.h | 6 +- Telegram/SourceFiles/calls/calls_instance.cpp | 53 +-- Telegram/SourceFiles/calls/calls_instance.h | 1 + Telegram/SourceFiles/facades.cpp | 11 + Telegram/SourceFiles/facades.h | 6 + Telegram/SourceFiles/info/info_top_bar.cpp | 2 + .../platform/linux/specific_linux.cpp | 25 ++ .../SourceFiles/platform/mac/specific_mac.mm | 9 + .../SourceFiles/platform/platform_specific.h | 5 + .../SourceFiles/platform/win/specific_win.cpp | 7 + Telegram/SourceFiles/settings/settings.style | 11 + .../SourceFiles/settings/settings_calls.cpp | 311 ++++++++++++++++++ .../SourceFiles/settings/settings_calls.h | 50 +++ .../SourceFiles/settings/settings_common.cpp | 3 + .../SourceFiles/settings/settings_common.h | 1 + .../SourceFiles/settings/settings_main.cpp | 4 + Telegram/SourceFiles/storage/localstorage.cpp | 49 +++ .../SourceFiles/ui/widgets/level_meter.cpp | 42 +++ Telegram/SourceFiles/ui/widgets/level_meter.h | 28 ++ Telegram/SourceFiles/ui/widgets/widgets.style | 18 + Telegram/ThirdParty/libtgvoip | 2 +- Telegram/gyp/telegram_sources.txt | 6 + 26 files changed, 728 insertions(+), 50 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/single_choice_box.cpp create mode 100644 Telegram/SourceFiles/boxes/single_choice_box.h create mode 100644 Telegram/SourceFiles/settings/settings_calls.cpp create mode 100644 Telegram/SourceFiles/settings/settings_calls.h create mode 100644 Telegram/SourceFiles/ui/widgets/level_meter.cpp create mode 100644 Telegram/SourceFiles/ui/widgets/level_meter.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 650fcadb6..26b51af33 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -331,6 +331,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_bg_tile" = "Tile background"; "lng_settings_adaptive_wide" = "Adaptive layout for wide screens"; +"lng_settings_section_call_settings" = "Calls Settings"; +"lng_settings_call_section_output" = "Speakers and headphones"; +"lng_settings_call_section_input" = "Microphone"; +"lng_settings_call_input_device" = "Input device"; +"lng_settings_call_output_device" = "Output device"; +"lng_settings_call_input_volume" = "Input volume: {percent}%"; +"lng_settings_call_output_volume" = "Output volume: {percent}%"; +"lng_settings_call_test_mic" = "Test microphone"; +"lng_settings_call_stop_mic_test" = "Stop test"; +"lng_settings_call_section_other" = "Other settings"; +"lng_settings_call_open_system_prefs" = "Open system sound preferences"; +"lng_settings_call_device_default" = "Default"; +"lng_settings_call_audio_ducking" = "Mute other sounds during calls"; + "lng_settings_language" = "Language"; "lng_settings_default_scale" = "Default interface scale"; "lng_settings_connection_type" = "Connection type"; @@ -1872,6 +1886,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_linux_menu_undo" = "Undo"; "lng_linux_menu_redo" = "Redo"; +"lng_linux_no_audio_prefs" = "You don't have any audio configuration applications installed."; + // Mac specific "lng_mac_choose_program_menu" = "Other..."; diff --git a/Telegram/SourceFiles/boxes/single_choice_box.cpp b/Telegram/SourceFiles/boxes/single_choice_box.cpp new file mode 100644 index 000000000..926d9d683 --- /dev/null +++ b/Telegram/SourceFiles/boxes/single_choice_box.cpp @@ -0,0 +1,39 @@ +/* +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 "boxes/single_choice_box.h" + +#include "lang/lang_keys.h" +#include "storage/localstorage.h" +#include "mainwindow.h" +#include "ui/widgets/checkbox.h" +#include "styles/style_boxes.h" + +void SingleChoiceBox::prepare() { + setTitle(langFactory(_title)); + + addButton(langFactory(lng_box_ok), [this] { closeBox(); }); + + auto group = std::make_shared(_initialSelection); + auto y = st::boxOptionListPadding.top() + st::autolockButton.margin.top(); + auto count = int(_optionTexts.size()); + _options.reserve(count); + auto i = 0; + for (const auto &text : _optionTexts) { + _options.emplace_back(this, group, i, text, st::autolockButton); + _options.back()->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), y); + y += _options.back()->heightNoMargins() + st::boxOptionListSkip; + i++; + } + group->setChangedCallback([this](int value) { + _callback(value); + closeBox(); + }); + + setDimensions(st::autolockWidth, st::boxOptionListPadding.top() + count * _options.back()->heightNoMargins() + (count - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom()); +} + diff --git a/Telegram/SourceFiles/boxes/single_choice_box.h b/Telegram/SourceFiles/boxes/single_choice_box.h new file mode 100644 index 000000000..549184a7e --- /dev/null +++ b/Telegram/SourceFiles/boxes/single_choice_box.h @@ -0,0 +1,35 @@ +/* +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 +*/ +#pragma once + +#include "boxes/abstract_box.h" +#include + +enum LangKey : int; + +namespace Ui { +class Radiobutton; +} // namespace Ui + +class SingleChoiceBox : public BoxContent { +public: + SingleChoiceBox(QWidget*, LangKey title, std::vector optionTexts, int initialSelection, Fn callback) : _title(title), _optionTexts(optionTexts), _initialSelection(initialSelection), _callback(callback) { + } + +protected: + void prepare() override; + +private: + LangKey _title; + std::vector _optionTexts; + int _initialSelection = 0; + Fn _callback; + std::vector> _options; + +}; + diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index a41b09f77..3d0b4bf9b 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -539,6 +539,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) { #endif // Q_OS_MAC config.enableNS = true; config.enableAGC = true; + config.enableVolumeControl = true; config.initTimeout = Global::CallConnectTimeoutMs() / 1000; config.recvTimeout = Global::CallPacketTimeoutMs() / 1000; if (Logs::DebugEnabled()) { @@ -587,6 +588,13 @@ void Call::createAndStartController(const MTPDphoneCall &call) { call.is_p2p_allowed(), protocol.vmax_layer.v); _controller->SetConfig(config); + _controller->SetCurrentAudioOutput(Global::CallOutputDeviceID().toStdString()); + _controller->SetCurrentAudioInput(Global::CallInputDeviceID().toStdString()); + _controller->SetOutputVolume(Global::CallOutputVolume()/100.0f); + _controller->SetInputVolume(Global::CallInputVolume()/100.0f); +#ifdef Q_OS_MAC + _controller->SetAudioOutputDuckingEnabled(Global::CallAudioDuckingEnabled()); +#endif _controller->SetEncryptionKey(reinterpret_cast(_authKey.data()), (_type == Type::Outgoing)); _controller->SetCallbacks(callbacks); if (Global::UseProxyForCalls() @@ -751,6 +759,34 @@ void Call::setState(State state) { } } +void Call::setCurrentAudioDevice(bool input, std::string deviceID){ + if (_controller) { + if (input) { + _controller->SetCurrentAudioInput(deviceID); + } else { + _controller->SetCurrentAudioOutput(deviceID); + } + } +} + +void Call::setAudioVolume(bool input, float level){ + if (_controller) { + if(input) { + _controller->SetInputVolume(level); + } else { + _controller->SetOutputVolume(level); + } + } +} + +void Call::setAudioDuckingEnabled(bool enabled){ +#ifdef Q_OS_MAC + if (_controller) { + _controller->SetAudioOutputDuckingEnabled(enabled); + } +#endif +} + void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { Expects(type != FinishType::None); @@ -844,7 +880,7 @@ Call::~Call() { destroyController(); } -void UpdateConfig(const std::map &data) { +void UpdateConfig(const std::string& data) { tgvoip::ServerConfig::GetSharedInstance()->Update(data); } diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 1ab360bd5..90f23e782 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -121,6 +121,10 @@ public: bytes::vector getKeyShaForFingerprint() const; QString getDebugLog() const; + + void setCurrentAudioDevice(bool input, std::string deviceID); + void setAudioVolume(bool input, float level); + void setAudioDuckingEnabled(bool enabled); ~Call(); @@ -211,6 +215,6 @@ private: }; -void UpdateConfig(const std::map &data); +void UpdateConfig(const std::string& data); } // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index f51bf1f0a..83de10a7c 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -189,53 +189,8 @@ void Instance::refreshServerConfig() { _serverConfigRequestId = 0; _lastServerConfigUpdateTime = getms(true); - auto configUpdate = std::map(); - auto bytes = bytes::make_span(result.c_dataJSON().vdata.v); - auto error = QJsonParseError { 0, QJsonParseError::NoError }; - auto document = QJsonDocument::fromJson(QByteArray::fromRawData(reinterpret_cast(bytes.data()), bytes.size()), &error); - if (error.error != QJsonParseError::NoError) { - LOG(("API Error: Failed to parse call config JSON, error: %1").arg(error.errorString())); - return; - } else if (!document.isObject()) { - LOG(("API Error: Not an object received in call config JSON.")); - return; - } - - auto parseValue = [](QJsonValueRef data) -> std::string { - switch (data.type()) { - case QJsonValue::String: return data.toString().toStdString(); - case QJsonValue::Double: return QString::number(data.toDouble(), 'f').toStdString(); - case QJsonValue::Bool: return data.toBool() ? "true" : "false"; - case QJsonValue::Null: { - LOG(("API Warning: null field in call config JSON.")); - } return "null"; - case QJsonValue::Undefined: { - LOG(("API Warning: undefined field in call config JSON.")); - } return "undefined"; - case QJsonValue::Object: - case QJsonValue::Array: { - LOG(("API Warning: complex field in call config JSON.")); - QJsonDocument serializer; - if (data.isArray()) { - serializer.setArray(data.toArray()); - } else { - serializer.setObject(data.toObject()); - } - auto byteArray = serializer.toJson(QJsonDocument::Compact); - return std::string(byteArray.constData(), byteArray.size()); - } break; - } - Unexpected("Type in Json parse."); - }; - - auto object = document.object(); - for (auto i = object.begin(), e = object.end(); i != e; ++i) { - auto key = i.key().toStdString(); - auto value = parseValue(i.value()); - configUpdate[key] = value; - } - - UpdateConfig(configUpdate); + const auto &json = result.c_dataJSON().vdata.v; + UpdateConfig(std::string(json.data(), json.size())); }).fail([this](const RPCError &error) { _serverConfigRequestId = 0; }).send(); @@ -289,6 +244,10 @@ bool Instance::alreadyInCall() { return (_currentCall && _currentCall->state() != Call::State::Busy); } +Call *Instance::currentCall() { + return _currentCall.get(); +} + void Instance::requestMicrophonePermissionOrFail(Fn onSuccess) { Platform::PermissionStatus status=Platform::GetPermissionStatus(Platform::PermissionType::Microphone); if (status==Platform::PermissionStatus::Granted) { diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 92f49dc54..c59f496d2 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -27,6 +27,7 @@ public: void startOutgoingCall(not_null user); void handleUpdate(const MTPDupdatePhoneCall &update); void showInfoPanel(not_null call); + Call* currentCall(); base::Observable ¤tCallChanged() { return _currentCallChanged; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 02c024363..e057081ac 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -661,6 +661,11 @@ struct Data { base::Observable UnreadCounterUpdate; base::Observable PeerChooseCancel; + QString CallOutputDeviceID = qsl("default"); + QString CallInputDeviceID = qsl("default"); + int CallOutputVolume = 100; + int CallInputVolume = 100; + bool CallAudioDuckingEnabled = true; }; } // namespace internal @@ -789,6 +794,12 @@ DefineRefVar(Global, base::Variable, WorkMode); DefineRefVar(Global, base::Observable, UnreadCounterUpdate); DefineRefVar(Global, base::Observable, PeerChooseCancel); + +DefineVar(Global, QString, CallOutputDeviceID); +DefineVar(Global, QString, CallInputDeviceID); +DefineVar(Global, int, CallOutputVolume); +DefineVar(Global, int, CallInputVolume); +DefineVar(Global, bool, CallAudioDuckingEnabled); rpl::producer ReplaceEmojiValue() { return rpl::single( diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index c7bcb61a5..f05d98b63 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -323,6 +323,12 @@ DeclareRefVar(base::Variable, WorkMode); DeclareRefVar(base::Observable, UnreadCounterUpdate); DeclareRefVar(base::Observable, PeerChooseCancel); + +DeclareVar(QString, CallOutputDeviceID); +DeclareVar(QString, CallInputDeviceID); +DeclareVar(int, CallOutputVolume); +DeclareVar(int, CallInputVolume); +DeclareVar(bool, CallAudioDuckingEnabled); rpl::producer ReplaceEmojiValue(); diff --git a/Telegram/SourceFiles/info/info_top_bar.cpp b/Telegram/SourceFiles/info/info_top_bar.cpp index 2308ac6a9..dcf549d68 100644 --- a/Telegram/SourceFiles/info/info_top_bar.cpp +++ b/Telegram/SourceFiles/info/info_top_bar.cpp @@ -611,6 +611,8 @@ rpl::producer TitleValue( return lng_settings_advanced; case Section::SettingsType::Chat: return lng_settings_section_chat_settings; + case Section::SettingsType::Calls: + return lng_settings_section_call_settings; } Unexpected("Bad settings type in Info::TitleValue()"); } diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 11b730df1..606281a93 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "application.h" #include "mainwidget.h" #include "mainwindow.h" +#include "platform/linux/linux_desktop_environment.h" #include "platform/linux/file_utilities_linux.h" #include "platform/platform_notifications_manager.h" #include "storage/localstorage.h" @@ -488,6 +489,30 @@ void RequestPermission(PermissionType type, Fn resultCal void OpenSystemSettingsForPermission(PermissionType type) { } +bool OpenSystemSettings(SystemSettingsType type) { + if (type == SystemSettingsType::Audio) { + bool succeeded = false; + if (DesktopEnvironment::IsUnity()) { + succeeded |= QProcess::startDetached(qsl("unity-control-center sound")); + } else if (DesktopEnvironment::IsKDE()) { + succeeded |= QProcess::startDetached(qsl("kcmshell5 kcm_pulseaudio")); + if (!succeeded) { + succeeded |= QProcess::startDetached(qsl("kcmshell4 phonon")); + } + } else if (DesktopEnvironment::IsGnome()) { + succeeded |= QProcess::startDetached(qsl("gnome-control-center sound")); + } + if (!succeeded) { + succeeded |= QProcess::startDetached(qsl("pavucontrol")); + } + if (!succeeded) { + succeeded |= QProcess::startDetached(qsl("alsamixergui")); + } + return succeeded; + } + return true; +} + namespace ThirdParty { void start() { diff --git a/Telegram/SourceFiles/platform/mac/specific_mac.mm b/Telegram/SourceFiles/platform/mac/specific_mac.mm index c0726628b..c641515e8 100644 --- a/Telegram/SourceFiles/platform/mac/specific_mac.mm +++ b/Telegram/SourceFiles/platform/mac/specific_mac.mm @@ -332,6 +332,15 @@ void OpenSystemSettingsForPermission(PermissionType type) { #endif // OS_MAC_OLD } +bool OpenSystemSettings(SystemSettingsType type) { + switch (type) { + case SystemSettingsType::Audio: + [[NSWorkspace sharedWorkspace] openFile:@"/System/Library/PreferencePanes/Sound.prefPane"]; + break; + } + return true; +} + } // namespace Platform void psNewVersion() { diff --git a/Telegram/SourceFiles/platform/platform_specific.h b/Telegram/SourceFiles/platform/platform_specific.h index dfb625a50..848ff2194 100644 --- a/Telegram/SourceFiles/platform/platform_specific.h +++ b/Telegram/SourceFiles/platform/platform_specific.h @@ -22,6 +22,10 @@ enum class PermissionType { Microphone, }; +enum class SystemSettingsType { + Audio, +}; + void SetWatchingMediaKeys(bool watching); bool IsApplicationActive(); void SetApplicationIcon(const QIcon &icon); @@ -34,6 +38,7 @@ void RegisterCustomScheme(); PermissionStatus GetPermissionStatus(PermissionType type); void RequestPermission(PermissionType type, Fn resultCallback); void OpenSystemSettingsForPermission(PermissionType type); +bool OpenSystemSettings(SystemSettingsType type); QString SystemLanguage(); QString SystemCountry(); diff --git a/Telegram/SourceFiles/platform/win/specific_win.cpp b/Telegram/SourceFiles/platform/win/specific_win.cpp index 683097d6a..d7c5917fc 100644 --- a/Telegram/SourceFiles/platform/win/specific_win.cpp +++ b/Telegram/SourceFiles/platform/win/specific_win.cpp @@ -657,6 +657,13 @@ void OpenSystemSettingsForPermission(PermissionType type) { } } +bool OpenSystemSettings(SystemSettingsType type) { + if (type == SystemSettingsType::Audio) { + WinExec("control.exe mmsys.cpl", SW_SHOW); + } + return true; +} + } // namespace Platform void psNewVersion() { diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 4f760c3f2..4cf66f22e 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -59,6 +59,7 @@ settingsIconInterfaceScale: icon {{ "settings_interface_scale", menuIconFg }}; settingsIconFaq: icon {{ "settings_faq", menuIconFg }}; settingsIconStickers: icon {{ "settings_stickers", menuIconFg }}; settingsIconThemes: icon {{ "settings_themes", menuIconFg }}; +settingsIconCalls: icon {{ "settings_phone_number", menuIconFg }}; settingsSetPhotoSkip: 7px; @@ -191,3 +192,13 @@ settingsThemeMinSkip: 4px; autoDownloadLimitButton: InfoProfileButton(settingsButton) { padding: margins(22px, 10px, 22px, 0px); } +settingsAudioVolumeSlider: MediaSlider(defaultContinuousSlider) { + seekSize: size(15px, 15px); +} +settingsAudioVolumeSliderPadding: margins(23px, 5px, 20px, 10px); +settingsAudioVolumeLabel: LabelSimple(defaultLabelSimple) { + font: boxTextFont; + textFg: windowBoldFg; +} +settingsAudioVolumeLabelPadding: margins(22px, 11px, 22px, 11px); +settingsLevelMeterPadding: margins(23px, 10px, 20px, 10px); diff --git a/Telegram/SourceFiles/settings/settings_calls.cpp b/Telegram/SourceFiles/settings/settings_calls.cpp new file mode 100644 index 000000000..e8fcb8a27 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_calls.cpp @@ -0,0 +1,311 @@ +/* +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 "info/profile/info_profile_button.h" +#include "boxes/single_choice_box.h" +#include "boxes/confirm_box.h" +#include "platform/platform_specific.h" +#include "lang/lang_keys.h" +#include "storage/localstorage.h" +#include "layout.h" +#include "styles/style_settings.h" +#include "ui/widgets/continuous_sliders.h" +#include "calls/calls_instance.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, UserData *self) +: Section(parent) { + setupContent(); +} + +Calls::~Calls(){ + if (_needWriteSettings) { + Local::writeUserSettings(); + } +} + +void Calls::sectionSaveChanges(FnMut done){ + if (_micTester) { + _micTester.reset(); + } + done(); +} + +void Calls::setupContent() { + const auto content = Ui::CreateChild(this); + + QString currentOutputName; + if (Global::CallOutputDeviceID() == qsl("default")) { + currentOutputName = lang(lng_settings_call_device_default); + } else { + std::vector outputDevices = tgvoip::VoIPController::EnumerateAudioOutputs(); + currentOutputName=Global::CallOutputDeviceID(); + for (auto &dev : outputDevices) { + if (QString::fromUtf8(dev.id.c_str()) == Global::CallOutputDeviceID()) { + currentOutputName = QString::fromUtf8(dev.displayName.c_str()); + break; + } + } + } + + QString currentInputName; + if (Global::CallInputDeviceID() == qsl("default")) { + currentInputName = lang(lng_settings_call_device_default); + } else { + std::vector inputDevices = tgvoip::VoIPController::EnumerateAudioInputs(); + currentInputName = Global::CallInputDeviceID(); + for (auto &dev : inputDevices) { + if (QString::fromUtf8(dev.id.c_str()) == Global::CallInputDeviceID()) { + currentInputName = QString::fromUtf8(dev.displayName.c_str()); + break; + } + } + } + + AddSkip(content); + AddSubsectionTitle(content, lng_settings_call_section_output); + const auto outputButton = AddButtonWithLabel( + content, + lng_settings_call_output_device, + rpl::single(currentOutputName) | rpl::then(_outputNameStream.events()), + st::settingsButton); + outputButton->addClickHandler([this] { + int selectedOption = 0; + std::vector devices = tgvoip::VoIPController::EnumerateAudioOutputs(); + std::vector options; + options.push_back(lang(lng_settings_call_device_default)); + int i = 1; + for (auto &device : devices) { + QString displayName = QString::fromUtf8(device.displayName.c_str()); + options.push_back(displayName); + if (QString::fromUtf8(device.id.c_str()) == Global::CallOutputDeviceID()) { + selectedOption = i; + } + i++; + } + const auto save = crl::guard(this, [=](int selectedOption) { + QString name = options[selectedOption]; + _outputNameStream.fire(std::move(name)); + std::string selectedDeviceID; + if (selectedOption == 0) { + selectedDeviceID = "default"; + } else { + selectedDeviceID = devices[selectedOption-1].id; + } + Global::SetCallOutputDeviceID(QString::fromStdString(selectedDeviceID)); + Local::writeUserSettings(); + + ::Calls::Call *currentCall = ::Calls::Current().currentCall(); + if (currentCall) { + currentCall->setCurrentAudioDevice(false, selectedDeviceID); + } + }); + Ui::show(Box(lng_settings_call_output_device, options, selectedOption, 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); + auto updateOutputLabel = [outputLabel](int value){ + QString percent = QString::number(value); + outputLabel->setText(lng_settings_call_output_volume(lt_percent, percent)); + }; + outputSlider->resize(st::settingsAudioVolumeSlider.seekSize); + outputSlider->setPseudoDiscrete( + 201, + [](int val){ + return val; + }, + Global::CallOutputVolume(), + [updateOutputLabel, this](int value) { + _needWriteSettings = true; + updateOutputLabel(value); + Global::SetCallOutputVolume(value); + ::Calls::Call* currentCall = ::Calls::Current().currentCall(); + if (currentCall) { + currentCall->setAudioVolume(false, value/100.0f); + } + }); + updateOutputLabel(Global::CallOutputVolume()); + + AddSkip(content); + AddDivider(content); + AddSkip(content); + AddSubsectionTitle(content, lng_settings_call_section_input); + const auto inputButton = AddButtonWithLabel( + content, + lng_settings_call_input_device, + rpl::single(currentInputName) | rpl::then(_inputNameStream.events()), + st::settingsButton); + inputButton->addClickHandler([this] { + int selectedOption = 0; + std::vector devices = tgvoip::VoIPController::EnumerateAudioInputs(); + std::vector options; + options.push_back(lang(lng_settings_call_device_default)); + int i = 1; + for (auto &device : devices) { + QString displayName = QString::fromUtf8(device.displayName.c_str()); + options.push_back(displayName); + if(QString::fromUtf8(device.id.c_str()) == Global::CallInputDeviceID()) + selectedOption = i; + i++; + } + const auto save = crl::guard(this, [=](int selectedOption) { + QString name=options[selectedOption]; + _inputNameStream.fire(std::move(name)); + std::string selectedDeviceID; + if (selectedOption == 0) { + selectedDeviceID = "default"; + } else { + selectedDeviceID = devices[selectedOption - 1].id; + } + Global::SetCallInputDeviceID(QString::fromUtf8(selectedDeviceID.c_str())); + Local::writeUserSettings(); + if (_micTester) { + stopTestingMicrophone(); + } + ::Calls::Call *currentCall = ::Calls::Current().currentCall(); + if(currentCall){ + currentCall->setCurrentAudioDevice(true, selectedDeviceID); + } + }); + Ui::show(Box(lng_settings_call_input_device, options, selectedOption, 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); + auto updateInputLabel = [inputLabel](int value){ + QString percent = QString::number(value); + inputLabel->setText(lng_settings_call_input_volume(lt_percent, percent)); + }; + inputSlider->resize(st::settingsAudioVolumeSlider.seekSize); + inputSlider->setPseudoDiscrete(101, + [](int val){ + return val; + }, + Global::CallInputVolume(), + [updateInputLabel, this](int value) { + _needWriteSettings = true; + updateInputLabel(value); + Global::SetCallInputVolume(value); + ::Calls::Call *currentCall = ::Calls::Current().currentCall(); + if (currentCall) { + currentCall->setAudioVolume(true, value / 100.0f); + } + }); + updateInputLabel(Global::CallInputVolume()); + + _micTestButton=AddButton(content, rpl::single(lang(lng_settings_call_test_mic)) | rpl::then(_micTestTextStream.events()), st::settingsButton); + + _micTestLevel=content->add(object_ptr(content, st::defaultLevelMeter), st::settingsLevelMeterPadding); + _micTestLevel->resize(QSize(0, st::defaultLevelMeter.height)); + + _micTestButton->addClickHandler([this]{ + if (!_micTester) { + requestPermissionAndStartTestingMicrophone(); + } else { + stopTestingMicrophone(); + } + }); + _levelUpdateTimer.setCallback([this](){ + _micTestLevel->setValue(_micTester->GetAndResetLevel()); + }); + + AddSkip(content); + AddDivider(content); + AddSkip(content); + AddSubsectionTitle(content, lng_settings_call_section_other); + +#ifdef Q_OS_MAC + AddButton( + content, + lng_settings_call_audio_ducking, + st::settingsButton + )->toggleOn( + rpl::single(Global::CallAudioDuckingEnabled()) + )->toggledValue() | rpl::filter([](bool enabled) { + return (enabled != Global::CallAudioDuckingEnabled()); + }) | rpl::start_with_next([](bool enabled) { + Global::SetCallAudioDuckingEnabled(enabled); + Local::writeUserSettings(); + ::Calls::Call *currentCall = ::Calls::Current().currentCall(); + if (currentCall) { + currentCall->setAudioDuckingEnabled(enabled); + } + }, content->lifetime()); +#endif // Q_OS_MAC + + const auto systemSettingsButton=AddButton(content, lng_settings_call_open_system_prefs, st::settingsButton); + systemSettingsButton->addClickHandler([]{ + if (!Platform::OpenSystemSettings(Platform::SystemSettingsType::Audio)) { + Ui::show(Box(lang(lng_linux_no_audio_prefs))); + } + }); + AddSkip(content); + + Ui::ResizeFitChild(this, content); +} + +void Calls::requestPermissionAndStartTestingMicrophone(){ + Platform::PermissionStatus status = Platform::GetPermissionStatus(Platform::PermissionType::Microphone); + if (status == Platform::PermissionStatus::Granted) { + startTestingMicrophone(); + } else if (status == Platform::PermissionStatus::CanRequest) { + Platform::RequestPermission(Platform::PermissionType::Microphone, crl::guard(this, [this](Platform::PermissionStatus status) { + if (status == Platform::PermissionStatus::Granted) { + crl::on_main(crl::guard(this, [this]{ + startTestingMicrophone(); + })); + } + })); + } else { + Ui::show(Box(lang(lng_no_mic_permission), lang(lng_menu_settings), crl::guard(this, [] { + Platform::OpenSystemSettingsForPermission(Platform::PermissionType::Microphone); + Ui::hideLayer(); + }))); + } +} + +void Calls::startTestingMicrophone(){ + _micTestTextStream.fire(lang(lng_settings_call_stop_mic_test)); + _levelUpdateTimer.callEach(50); + _micTester = std::make_unique(Global::CallInputDeviceID().toStdString()); + if (_micTester->Failed()) { + Ui::show(Box(lang(lng_call_error_audio_io))); + stopTestingMicrophone(); + } +} + +void Calls::stopTestingMicrophone(){ + _micTestTextStream.fire(lang(lng_settings_call_test_mic)); + _levelUpdateTimer.cancel(); + _micTester.reset(); + _micTestLevel->setValue(0.0f); +} + +} // namespace Settings + diff --git a/Telegram/SourceFiles/settings/settings_calls.h b/Telegram/SourceFiles/settings/settings_calls.h new file mode 100644 index 000000000..9d64f71ef --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_calls.h @@ -0,0 +1,50 @@ +/* +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 +*/ +#pragma once + +#include "settings/settings_common.h" +#include "base/timer.h" + +namespace Calls { + class Call; +} // namespace Calls + +namespace Ui { + class LevelMeter; +} + +namespace tgvoip { + class AudioInputTester; +} + +namespace Settings { + +class Calls : public Section { +public: + explicit Calls(QWidget *parent, UserData *self = nullptr); + virtual ~Calls(); + virtual void sectionSaveChanges(FnMut done) override; + +private: + void setupContent(); + void requestPermissionAndStartTestingMicrophone(); + void startTestingMicrophone(); + void stopTestingMicrophone(); + + rpl::event_stream _outputNameStream; + rpl::event_stream _inputNameStream; + rpl::event_stream _micTestTextStream; + bool _needWriteSettings = false; + std::unique_ptr _micTester; + Button *_micTestButton = nullptr; + Ui::LevelMeter *_micTestLevel = nullptr; + base::Timer _levelUpdateTimer; +}; + +} // namespace Settings + diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index cc6e31d5c..08b7226c7 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_main.h" #include "settings/settings_notifications.h" #include "settings/settings_privacy_security.h" +#include "settings/settings_calls.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/widgets/labels.h" @@ -44,6 +45,8 @@ object_ptr
CreateSection( return object_ptr(parent, self); case Type::Chat: return object_ptr(parent, self); + case Type::Calls: + return object_ptr(parent, self); } Unexpected("Settings section type in Widget::createInnerWidget."); } diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index de21aaf1a..e09180b0b 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -38,6 +38,7 @@ enum class Type { PrivacySecurity, Advanced, Chat, + Calls, }; using Button = Info::Profile::Button; diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index a1d1874ae..1618393b5 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -87,6 +87,10 @@ void SetupSections( lng_settings_section_chat_settings, Type::Chat, &st::settingsIconChat); + addSection( + lng_settings_section_call_settings, + Type::Calls, + &st::settingsIconCalls); addSection( lng_settings_advanced, Type::Advanced, diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index 3680b3a33..284e66513 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -601,6 +601,7 @@ enum { dbiScalePercent = 0x58, dbiPlaybackSpeed = 0x59, dbiLanguagesKey = 0x5a, + dbiCallSettings = 0x5b, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -886,6 +887,43 @@ void applyReadContext(ReadSettingsContext &&context) { } } +QByteArray serializeCallSettings(){ + QByteArray result=QByteArray(); + uint32 size = 3*sizeof(qint32) + Serialize::stringSize(Global::CallOutputDeviceID()) + Serialize::stringSize(Global::CallInputDeviceID()); + result.reserve(size); + QDataStream stream(&result, QIODevice::WriteOnly); + stream.setVersion(QDataStream::Qt_5_1); + stream << Global::CallOutputDeviceID(); + stream << qint32(Global::CallOutputVolume()); + stream << Global::CallInputDeviceID(); + stream << qint32(Global::CallInputVolume()); + stream << qint32(Global::CallAudioDuckingEnabled() ? 1 : 0); + return result; +} + +void deserializeCallSettings(QByteArray& settings){ + QDataStream stream(&settings, QIODevice::ReadOnly); + stream.setVersion(QDataStream::Qt_5_1); + QString outputDeviceID; + QString inputDeviceID; + qint32 outputVolume; + qint32 inputVolume; + qint32 duckingEnabled; + + stream >> outputDeviceID; + stream >> outputVolume; + stream >> inputDeviceID; + stream >> inputVolume; + stream >> duckingEnabled; + if(_checkStreamStatus(stream)){ + Global::SetCallOutputDeviceID(outputDeviceID); + Global::SetCallOutputVolume(outputVolume); + Global::SetCallInputDeviceID(inputDeviceID); + Global::SetCallInputVolume(inputVolume); + Global::SetCallAudioDuckingEnabled(duckingEnabled); + } +} + bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSettingsContext &context) { switch (blockId) { case dbiDcOptionOldOld: { @@ -1786,6 +1824,14 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting Global::SetVoiceMsgPlaybackDoubled(v == 2); } break; + case dbiCallSettings: { + QByteArray callSettings; + stream >> callSettings; + if(!_checkStreamStatus(stream)) return false; + + deserializeCallSettings(callSettings); + } break; + default: LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId)); return false; @@ -2002,6 +2048,7 @@ void _writeUserSettings() { auto userData = userDataInstance ? userDataInstance->serialize() : QByteArray(); + auto callSettings = serializeCallSettings(); uint32 size = 23 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); @@ -2024,6 +2071,7 @@ void _writeUserSettings() { if (!userData.isEmpty()) { size += sizeof(quint32) + Serialize::bytearraySize(userData); } + size += sizeof(quint32) + Serialize::bytearraySize(callSettings); EncryptedDescriptor data(size); data.stream @@ -2073,6 +2121,7 @@ void _writeUserSettings() { if (!Global::HiddenPinnedMessages().isEmpty()) { data.stream << quint32(dbiHiddenPinnedMessages) << Global::HiddenPinnedMessages(); } + data.stream << qint32(dbiCallSettings) << callSettings; FileWriteDescriptor file(_userSettingsKey); file.writeEncrypted(data); diff --git a/Telegram/SourceFiles/ui/widgets/level_meter.cpp b/Telegram/SourceFiles/ui/widgets/level_meter.cpp new file mode 100644 index 000000000..3eca2a0ed --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/level_meter.cpp @@ -0,0 +1,42 @@ +/* +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 "ui/widgets/level_meter.h" + +namespace Ui { + +LevelMeter::LevelMeter(QWidget *parent, const style::LevelMeter &st) : RpWidget(parent), _st(st){ + +} + +void LevelMeter::setValue(float value){ + _value = value; + repaint(); +} + +void LevelMeter::paintEvent(QPaintEvent* event){ + Painter p(this); + PainterHighQualityEnabler hq(p); + + p.setPen(Qt::NoPen); + + auto activeFg = _st.activeFg; + auto inactiveFg = _st.inactiveFg; + auto radius = _st.lineWidth / 2; + QRect rect(0, 0, _st.lineWidth, height()); + p.setBrush(activeFg); + for (auto i = 0; i < _st.lineCount; ++i) { + float valueAtLine = (float)(i + 1) / _st.lineCount; + if (valueAtLine > _value) { + p.setBrush(inactiveFg); + } + rect.moveLeft((_st.lineWidth + _st.lineSpacing) * i); + p.drawRoundedRect(rect, radius, radius); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/level_meter.h b/Telegram/SourceFiles/ui/widgets/level_meter.h new file mode 100644 index 000000000..4e2556e61 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/level_meter.h @@ -0,0 +1,28 @@ +/* +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 +*/ +#pragma once + +#include "styles/style_widgets.h" +#include "ui/rp_widget.h" + +namespace Ui { + +class LevelMeter : public RpWidget { +public: + LevelMeter(QWidget *parent, const style::LevelMeter& st); + void setValue(float value); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + const style::LevelMeter &_st; + float _value=0.0f; +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index dfad6417f..a22acdc96 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -1197,3 +1197,21 @@ InfoTopBar { highlightBg: color; highlightDuration: int; } + +LevelMeter { + height: pixels; + lineWidth: pixels; + lineSpacing: pixels; + lineCount: int; + activeFg: color; + inactiveFg: color; +} + +defaultLevelMeter: LevelMeter { + height: 18px; + lineWidth: 3px; + lineSpacing: 5px; + lineCount: 44; + activeFg: mediaPlayerActiveFg; + inactiveFg: mediaPlayerInactiveFg; +} diff --git a/Telegram/ThirdParty/libtgvoip b/Telegram/ThirdParty/libtgvoip index 78e584c44..59a975bf6 160000 --- a/Telegram/ThirdParty/libtgvoip +++ b/Telegram/ThirdParty/libtgvoip @@ -1 +1 @@ -Subproject commit 78e584c443b93ce2794bee75c7448d1b00f1edc9 +Subproject commit 59a975bf66c19ebddd8c82d9d501fddc02584d7c diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 621bc0b8b..974712e40 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -62,6 +62,8 @@ <(src_loc)/boxes/sessions_box.h <(src_loc)/boxes/share_box.cpp <(src_loc)/boxes/share_box.h +<(src_loc)/boxes/single_choice_box.cpp +<(src_loc)/boxes/single_choice_box.h <(src_loc)/boxes/sticker_set_box.cpp <(src_loc)/boxes/sticker_set_box.h <(src_loc)/boxes/stickers_box.cpp @@ -576,6 +578,8 @@ <(src_loc)/settings/settings_advanced.h <(src_loc)/settings/settings_chat.cpp <(src_loc)/settings/settings_chat.h +<(src_loc)/settings/settings_calls.cpp +<(src_loc)/settings/settings_calls.h <(src_loc)/settings/settings_codes.cpp <(src_loc)/settings/settings_codes.h <(src_loc)/settings/settings_common.cpp @@ -688,6 +692,8 @@ <(src_loc)/ui/widgets/input_fields.h <(src_loc)/ui/widgets/labels.cpp <(src_loc)/ui/widgets/labels.h +<(src_loc)/ui/widgets/level_meter.cpp +<(src_loc)/ui/widgets/level_meter.h <(src_loc)/ui/widgets/menu.cpp <(src_loc)/ui/widgets/menu.h <(src_loc)/ui/widgets/multi_select.cpp