mirror of
https://github.com/vale981/tdesktop
synced 2025-03-04 17:21:40 -05:00
Add call settings (#5540)
This commit is contained in:
parent
8306e58b75
commit
11b991cddc
26 changed files with 728 additions and 50 deletions
|
@ -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...";
|
||||
|
|
39
Telegram/SourceFiles/boxes/single_choice_box.cpp
Normal file
39
Telegram/SourceFiles/boxes/single_choice_box.cpp
Normal 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());
|
||||
}
|
||||
|
35
Telegram/SourceFiles/boxes/single_choice_box.h
Normal file
35
Telegram/SourceFiles/boxes/single_choice_box.h
Normal 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;
|
||||
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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*> ¤tCallChanged() {
|
||||
return _currentCallChanged;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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()");
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
311
Telegram/SourceFiles/settings/settings_calls.cpp
Normal file
311
Telegram/SourceFiles/settings/settings_calls.cpp
Normal 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
|
||||
|
50
Telegram/SourceFiles/settings/settings_calls.h
Normal file
50
Telegram/SourceFiles/settings/settings_calls.h
Normal 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
|
||||
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ enum class Type {
|
|||
PrivacySecurity,
|
||||
Advanced,
|
||||
Chat,
|
||||
Calls,
|
||||
};
|
||||
|
||||
using Button = Info::Profile::Button;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
42
Telegram/SourceFiles/ui/widgets/level_meter.cpp
Normal file
42
Telegram/SourceFiles/ui/widgets/level_meter.cpp
Normal 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
|
28
Telegram/SourceFiles/ui/widgets/level_meter.h
Normal file
28
Telegram/SourceFiles/ui/widgets/level_meter.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
2
Telegram/ThirdParty/libtgvoip
vendored
2
Telegram/ThirdParty/libtgvoip
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 78e584c443b93ce2794bee75c7448d1b00f1edc9
|
||||
Subproject commit 59a975bf66c19ebddd8c82d9d501fddc02584d7c
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue