Add call settings (#5540)

This commit is contained in:
Gregory K 2019-01-05 14:08:02 +03:00 committed by John Preston
parent 8306e58b75
commit 11b991cddc
26 changed files with 728 additions and 50 deletions

View File

@ -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...";

View File

@ -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<Ui::RadiobuttonGroup>(_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());
}

View File

@ -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 <vector>
enum LangKey : int;
namespace Ui {
class Radiobutton;
} // namespace Ui
class SingleChoiceBox : public BoxContent {
public:
SingleChoiceBox(QWidget*, LangKey title, std::vector<QString> optionTexts, int initialSelection, Fn<void(int)> callback) : _title(title), _optionTexts(optionTexts), _initialSelection(initialSelection), _callback(callback) {
}
protected:
void prepare() override;
private:
LangKey _title;
std::vector<QString> _optionTexts;
int _initialSelection = 0;
Fn<void(int)> _callback;
std::vector<object_ptr<Ui::Radiobutton>> _options;
};

View File

@ -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<char*>(_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<std::string, std::string> &data) {
void UpdateConfig(const std::string& data) {
tgvoip::ServerConfig::GetSharedInstance()->Update(data);
}

View File

@ -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<std::string, std::string> &data);
void UpdateConfig(const std::string& data);
} // namespace Calls

View File

@ -189,53 +189,8 @@ void Instance::refreshServerConfig() {
_serverConfigRequestId = 0;
_lastServerConfigUpdateTime = getms(true);
auto configUpdate = std::map<std::string, std::string>();
auto bytes = bytes::make_span(result.c_dataJSON().vdata.v);
auto error = QJsonParseError { 0, QJsonParseError::NoError };
auto document = QJsonDocument::fromJson(QByteArray::fromRawData(reinterpret_cast<const char*>(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<void()> onSuccess) {
Platform::PermissionStatus status=Platform::GetPermissionStatus(Platform::PermissionType::Microphone);
if (status==Platform::PermissionStatus::Granted) {

View File

@ -27,6 +27,7 @@ public:
void startOutgoingCall(not_null<UserData*> user);
void handleUpdate(const MTPDupdatePhoneCall &update);
void showInfoPanel(not_null<Call*> call);
Call* currentCall();
base::Observable<Call*> &currentCallChanged() {
return _currentCallChanged;

View File

@ -661,6 +661,11 @@ struct Data {
base::Observable<void> UnreadCounterUpdate;
base::Observable<void> 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<DBIWorkMode>, WorkMode);
DefineRefVar(Global, base::Observable<void>, UnreadCounterUpdate);
DefineRefVar(Global, base::Observable<void>, PeerChooseCancel);
DefineVar(Global, QString, CallOutputDeviceID);
DefineVar(Global, QString, CallInputDeviceID);
DefineVar(Global, int, CallOutputVolume);
DefineVar(Global, int, CallInputVolume);
DefineVar(Global, bool, CallAudioDuckingEnabled);
rpl::producer<bool> ReplaceEmojiValue() {
return rpl::single(

View File

@ -323,6 +323,12 @@ DeclareRefVar(base::Variable<DBIWorkMode>, WorkMode);
DeclareRefVar(base::Observable<void>, UnreadCounterUpdate);
DeclareRefVar(base::Observable<void>, PeerChooseCancel);
DeclareVar(QString, CallOutputDeviceID);
DeclareVar(QString, CallInputDeviceID);
DeclareVar(int, CallOutputVolume);
DeclareVar(int, CallInputVolume);
DeclareVar(bool, CallAudioDuckingEnabled);
rpl::producer<bool> ReplaceEmojiValue();

View File

@ -611,6 +611,8 @@ rpl::producer<QString> 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()");
}

View File

@ -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<void(PermissionStatus)> 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() {

View File

@ -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() {

View File

@ -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<void(PermissionStatus)> resultCallback);
void OpenSystemSettingsForPermission(PermissionType type);
bool OpenSystemSettings(SystemSettingsType type);
QString SystemLanguage();
QString SystemCountry();

View File

@ -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() {

View File

@ -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);

View File

@ -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 <VoIPController.h>
#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<void()> done){
if (_micTester) {
_micTester.reset();
}
done();
}
void Calls::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
QString currentOutputName;
if (Global::CallOutputDeviceID() == qsl("default")) {
currentOutputName = lang(lng_settings_call_device_default);
} else {
std::vector<tgvoip::AudioOutputDevice> 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<tgvoip::AudioInputDevice> 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<tgvoip::AudioOutputDevice> devices = tgvoip::VoIPController::EnumerateAudioOutputs();
std::vector<QString> 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<SingleChoiceBox>(lng_settings_call_output_device, options, selectedOption, save));
});
const auto outputLabel = content->add(object_ptr<Ui::LabelSimple>(content, st::settingsAudioVolumeLabel), st::settingsAudioVolumeLabelPadding);
const auto outputSlider = content->add(object_ptr<Ui::MediaSlider>(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<tgvoip::AudioInputDevice> devices = tgvoip::VoIPController::EnumerateAudioInputs();
std::vector<QString> 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<SingleChoiceBox>(lng_settings_call_input_device, options, selectedOption, save));
});
const auto inputLabel = content->add(object_ptr<Ui::LabelSimple>(content, st::settingsAudioVolumeLabel), st::settingsAudioVolumeLabelPadding);
const auto inputSlider = content->add(object_ptr<Ui::MediaSlider>(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<Ui::LevelMeter>(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<InformBox>(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<ConfirmBox>(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<tgvoip::AudioInputTester>(Global::CallInputDeviceID().toStdString());
if (_micTester->Failed()) {
Ui::show(Box<InformBox>(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

View File

@ -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<void()> done) override;
private:
void setupContent();
void requestPermissionAndStartTestingMicrophone();
void startTestingMicrophone();
void stopTestingMicrophone();
rpl::event_stream<QString> _outputNameStream;
rpl::event_stream<QString> _inputNameStream;
rpl::event_stream<QString> _micTestTextStream;
bool _needWriteSettings = false;
std::unique_ptr<tgvoip::AudioInputTester> _micTester;
Button *_micTestButton = nullptr;
Ui::LevelMeter *_micTestLevel = nullptr;
base::Timer _levelUpdateTimer;
};
} // namespace Settings

View File

@ -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<Section> CreateSection(
return object_ptr<Advanced>(parent, self);
case Type::Chat:
return object_ptr<Chat>(parent, self);
case Type::Calls:
return object_ptr<Calls>(parent, self);
}
Unexpected("Settings section type in Widget::createInnerWidget.");
}

View File

@ -38,6 +38,7 @@ enum class Type {
PrivacySecurity,
Advanced,
Chat,
Calls,
};
using Button = Info::Profile::Button;

View File

@ -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,

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;
}

@ -1 +1 @@
Subproject commit 78e584c443b93ce2794bee75c7448d1b00f1edc9
Subproject commit 59a975bf66c19ebddd8c82d9d501fddc02584d7c

View File

@ -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