settings_calls.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "settings/settings_calls.h"
  8. #include "ui/wrap/vertical_layout.h"
  9. #include "ui/wrap/slide_wrap.h"
  10. #include "ui/widgets/labels.h"
  11. #include "ui/widgets/checkbox.h"
  12. #include "ui/widgets/level_meter.h"
  13. #include "ui/widgets/buttons.h"
  14. #include "ui/boxes/single_choice_box.h"
  15. #include "ui/boxes/confirm_box.h"
  16. #include "ui/vertical_list.h"
  17. #include "platform/platform_specific.h"
  18. #include "main/main_session.h"
  19. #include "lang/lang_keys.h"
  20. #include "styles/style_settings.h"
  21. #include "ui/widgets/continuous_sliders.h"
  22. #include "window/window_session_controller.h"
  23. #include "core/application.h"
  24. #include "core/core_settings.h"
  25. #include "calls/calls_call.h"
  26. #include "calls/calls_instance.h"
  27. #include "calls/calls_video_bubble.h"
  28. #include "apiwrap.h"
  29. #include "api/api_authorizations.h"
  30. #include "webrtc/webrtc_environment.h"
  31. #include "webrtc/webrtc_video_track.h"
  32. #include "webrtc/webrtc_audio_input_tester.h"
  33. #include "webrtc/webrtc_create_adm.h" // Webrtc::Backend.
  34. #include "tgcalls/VideoCaptureInterface.h"
  35. #include "styles/style_layers.h"
  36. namespace Settings {
  37. namespace {
  38. using namespace Webrtc;
  39. [[nodiscard]] rpl::producer<QString> DeviceNameValue(
  40. DeviceType type,
  41. rpl::producer<QString> id) {
  42. return std::move(id) | rpl::map([type](const QString &id) {
  43. return Core::App().mediaDevices().devicesValue(
  44. type
  45. ) | rpl::map([id](const std::vector<DeviceInfo> &list) {
  46. const auto i = ranges::find(list, id, &DeviceInfo::id);
  47. return (i != end(list) && !i->inactive)
  48. ? i->name
  49. : tr::lng_settings_call_device_default(tr::now);
  50. });
  51. }) | rpl::flatten_latest();
  52. }
  53. } // namespace
  54. Calls::Calls(
  55. QWidget *parent,
  56. not_null<Window::SessionController*> controller)
  57. : Section(parent)
  58. , _controller(controller) {
  59. // Request valid value of calls disabled flag.
  60. controller->session().api().authorizations().reload();
  61. setupContent();
  62. requestPermissionAndStartTestingMicrophone();
  63. }
  64. Calls::~Calls() = default;
  65. rpl::producer<QString> Calls::title() {
  66. return tr::lng_settings_section_devices();
  67. }
  68. Webrtc::VideoTrack *Calls::AddCameraSubsection(
  69. std::shared_ptr<Ui::Show> show,
  70. not_null<Ui::VerticalLayout*> content,
  71. bool saveToSettings) {
  72. auto &lifetime = content->lifetime();
  73. const auto hasCall = (Core::App().calls().currentCall() != nullptr);
  74. auto capturerOwner = lifetime.make_state<
  75. std::shared_ptr<tgcalls::VideoCaptureInterface>
  76. >();
  77. const auto track = lifetime.make_state<VideoTrack>(
  78. (hasCall
  79. ? VideoState::Inactive
  80. : VideoState::Active));
  81. const auto deviceId = lifetime.make_state<rpl::variable<QString>>(
  82. Core::App().settings().cameraDeviceId());
  83. auto resolvedId = rpl::deferred([=] {
  84. return DeviceIdOrDefault(deviceId->value());
  85. });
  86. AddButtonWithLabel(
  87. content,
  88. tr::lng_settings_call_input_device(),
  89. CameraDeviceNameValue(rpl::duplicate(resolvedId)),
  90. st::settingsButtonNoIcon
  91. )->addClickHandler([=] {
  92. show->show(ChooseCameraDeviceBox(
  93. rpl::duplicate(resolvedId),
  94. [=](const QString &id) {
  95. *deviceId = id;
  96. if (saveToSettings) {
  97. Core::App().settings().setCameraDeviceId(id);
  98. Core::App().saveSettingsDelayed();
  99. }
  100. if (*capturerOwner) {
  101. (*capturerOwner)->switchToDevice(
  102. id.toStdString(),
  103. false);
  104. }
  105. }));
  106. });
  107. const auto bubbleWrap = content->add(object_ptr<Ui::RpWidget>(content));
  108. const auto bubble = lifetime.make_state<::Calls::VideoBubble>(
  109. bubbleWrap,
  110. track);
  111. const auto padding = st::settingsButtonNoIcon.padding.left();
  112. const auto top = st::boxRoundShadow.extend.top();
  113. const auto bottom = st::boxRoundShadow.extend.bottom();
  114. auto frameSize = track->renderNextFrame(
  115. ) | rpl::map([=] {
  116. return track->frameSize();
  117. }) | rpl::filter([=](QSize size) {
  118. return !size.isEmpty()
  119. && !Core::App().calls().currentCall()
  120. && !Core::App().calls().currentGroupCall();
  121. });
  122. auto bubbleWidth = bubbleWrap->widthValue(
  123. ) | rpl::filter([=](int width) {
  124. return width > 2 * padding + 1;
  125. });
  126. rpl::combine(
  127. std::move(bubbleWidth),
  128. std::move(frameSize)
  129. ) | rpl::start_with_next([=](int width, QSize frame) {
  130. const auto useWidth = (width - 2 * padding);
  131. const auto useHeight = std::min(
  132. ((useWidth * frame.height()) / frame.width()),
  133. (useWidth * 480) / 640);
  134. bubbleWrap->resize(width, top + useHeight + bottom);
  135. bubble->updateGeometry(
  136. ::Calls::VideoBubble::DragMode::None,
  137. QRect(padding, top, useWidth, useHeight));
  138. bubbleWrap->update();
  139. }, bubbleWrap->lifetime());
  140. using namespace rpl::mappers;
  141. const auto checkCapturer = [=] {
  142. if (*capturerOwner
  143. || Core::App().calls().currentCall()
  144. || Core::App().calls().currentGroupCall()) {
  145. return;
  146. }
  147. *capturerOwner = Core::App().calls().getVideoCapture(
  148. Core::App().settings().cameraDeviceId(),
  149. false);
  150. (*capturerOwner)->setPreferredAspectRatio(0.);
  151. track->setState(VideoState::Active);
  152. (*capturerOwner)->setState(tgcalls::VideoState::Active);
  153. (*capturerOwner)->setOutput(track->sink());
  154. };
  155. rpl::combine(
  156. Core::App().calls().currentCallValue(),
  157. Core::App().calls().currentGroupCallValue(),
  158. _1 || _2
  159. ) | rpl::start_with_next([=](bool has) {
  160. if (has) {
  161. track->setState(VideoState::Inactive);
  162. bubbleWrap->resize(bubbleWrap->width(), 0);
  163. *capturerOwner = nullptr;
  164. } else {
  165. crl::on_main(content, checkCapturer);
  166. }
  167. }, lifetime);
  168. return track;
  169. }
  170. void Calls::sectionSaveChanges(FnMut<void()> done) {
  171. _testingMicrophone = false;
  172. done();
  173. }
  174. void Calls::setupContent() {
  175. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  176. const auto settings = &Core::App().settings();
  177. Ui::AddSkip(content);
  178. Ui::AddSubsectionTitle(content, tr::lng_settings_call_section_output());
  179. initPlaybackButton(
  180. content,
  181. tr::lng_settings_call_output_device(),
  182. rpl::deferred([=] {
  183. return DeviceIdOrDefault(settings->playbackDeviceIdValue());
  184. }),
  185. [=](const QString &id) { settings->setPlaybackDeviceId(id); });
  186. Ui::AddSkip(content);
  187. Ui::AddDivider(content);
  188. Ui::AddSkip(content);
  189. Ui::AddSubsectionTitle(content, tr::lng_settings_call_section_input());
  190. initCaptureButton(
  191. content,
  192. tr::lng_settings_call_input_device(),
  193. rpl::deferred([=] {
  194. return DeviceIdOrDefault(settings->captureDeviceIdValue());
  195. }),
  196. [=](const QString &id) { settings->setCaptureDeviceId(id); });
  197. Ui::AddSkip(content);
  198. Ui::AddDivider(content);
  199. Ui::AddSkip(content);
  200. Ui::AddSubsectionTitle(content, tr::lng_settings_devices_calls());
  201. const auto orDefault = [](const QString &value) {
  202. return value.isEmpty() ? kDefaultDeviceId : value;
  203. };
  204. const auto same = content->add(object_ptr<Ui::SettingsButton>(
  205. content,
  206. tr::lng_settings_devices_calls_same(),
  207. st::settingsButtonNoIcon));
  208. same->toggleOn(rpl::combine(
  209. settings->callPlaybackDeviceIdValue(),
  210. settings->callCaptureDeviceIdValue()
  211. ) | rpl::map([](const QString &playback, const QString &capture) {
  212. return playback.isEmpty() && capture.isEmpty();
  213. }));
  214. same->toggledValue() | rpl::filter([=](bool toggled) {
  215. const auto empty = settings->callPlaybackDeviceId().isEmpty()
  216. && settings->callCaptureDeviceId().isEmpty();
  217. return (empty != toggled);
  218. }) | rpl::start_with_next([=](bool toggled) {
  219. if (toggled) {
  220. settings->setCallPlaybackDeviceId(QString());
  221. settings->setCallCaptureDeviceId(QString());
  222. } else {
  223. settings->setCallPlaybackDeviceId(
  224. orDefault(settings->playbackDeviceId()));
  225. settings->setCallCaptureDeviceId(
  226. orDefault(settings->captureDeviceId()));
  227. }
  228. Core::App().saveSettingsDelayed();
  229. }, same->lifetime());
  230. const auto different = content->add(
  231. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  232. content,
  233. object_ptr<Ui::VerticalLayout>(content)));
  234. const auto calls = different->entity();
  235. initPlaybackButton(
  236. calls,
  237. tr::lng_group_call_speakers(),
  238. rpl::deferred([=] {
  239. return DeviceIdValueWithFallback(
  240. settings->callPlaybackDeviceIdValue(),
  241. settings->playbackDeviceIdValue());
  242. }),
  243. [=](const QString &id) { settings->setCallPlaybackDeviceId(id); });
  244. initCaptureButton(
  245. calls,
  246. tr::lng_group_call_microphone(),
  247. rpl::deferred([=] {
  248. return DeviceIdValueWithFallback(
  249. settings->callCaptureDeviceIdValue(),
  250. settings->captureDeviceIdValue());
  251. }),
  252. [=](const QString &id) { settings->setCallCaptureDeviceId(id); });
  253. different->toggleOn(same->toggledValue() | rpl::map(!rpl::mappers::_1));
  254. Ui::AddSkip(content);
  255. Ui::AddDivider(content);
  256. if (!Core::App().mediaDevices().defaultId(
  257. Webrtc::DeviceType::Camera).isEmpty()) {
  258. Ui::AddSkip(content);
  259. Ui::AddSubsectionTitle(content, tr::lng_settings_call_camera());
  260. AddCameraSubsection(_controller->uiShow(), content, true);
  261. Ui::AddSkip(content);
  262. Ui::AddDivider(content);
  263. }
  264. Ui::AddSkip(content);
  265. Ui::AddSubsectionTitle(content, tr::lng_settings_call_section_other());
  266. const auto api = &_controller->session().api();
  267. content->add(object_ptr<Ui::SettingsButton>(
  268. content,
  269. tr::lng_settings_call_accept_calls(),
  270. st::settingsButtonNoIcon
  271. ))->toggleOn(
  272. api->authorizations().callsDisabledHereValue(
  273. ) | rpl::map(!rpl::mappers::_1)
  274. )->toggledChanges(
  275. ) | rpl::filter([=](bool value) {
  276. return (value == api->authorizations().callsDisabledHere());
  277. }) | start_with_next([=](bool value) {
  278. api->authorizations().toggleCallsDisabledHere(!value);
  279. }, content->lifetime());
  280. content->add(object_ptr<Ui::SettingsButton>(
  281. content,
  282. tr::lng_settings_call_open_system_prefs(),
  283. st::settingsButtonNoIcon
  284. ))->addClickHandler([=] {
  285. using namespace ::Platform;
  286. const auto opened = OpenSystemSettings(SystemSettingsType::Audio);
  287. if (!opened) {
  288. _controller->show(
  289. Ui::MakeInformBox(tr::lng_linux_no_audio_prefs()));
  290. }
  291. });
  292. Ui::AddSkip(content);
  293. Ui::ResizeFitChild(this, content);
  294. }
  295. void Calls::initPlaybackButton(
  296. not_null<Ui::VerticalLayout*> container,
  297. rpl::producer<QString> text,
  298. rpl::producer<QString> resolvedId,
  299. Fn<void(QString)> set) {
  300. AddButtonWithLabel(
  301. container,
  302. tr::lng_settings_call_output_device(),
  303. PlaybackDeviceNameValue(rpl::duplicate(resolvedId)),
  304. st::settingsButtonNoIcon
  305. )->addClickHandler([=] {
  306. _controller->show(ChoosePlaybackDeviceBox(
  307. rpl::duplicate(resolvedId),
  308. [=](const QString &id) {
  309. set(id);
  310. Core::App().saveSettingsDelayed();
  311. }));
  312. });
  313. }
  314. void Calls::initCaptureButton(
  315. not_null<Ui::VerticalLayout*> container,
  316. rpl::producer<QString> text,
  317. rpl::producer<QString> resolvedId,
  318. Fn<void(QString)> set) {
  319. AddButtonWithLabel(
  320. container,
  321. tr::lng_settings_call_input_device(),
  322. CaptureDeviceNameValue(rpl::duplicate(resolvedId)),
  323. st::settingsButtonNoIcon
  324. )->addClickHandler([=] {
  325. _controller->show(ChooseCaptureDeviceBox(
  326. rpl::duplicate(resolvedId),
  327. [=](const QString &id) {
  328. set(id);
  329. Core::App().saveSettingsDelayed();
  330. }));
  331. });
  332. struct LevelState {
  333. std::unique_ptr<Webrtc::DeviceResolver> deviceId;
  334. std::unique_ptr<Webrtc::AudioInputTester> tester;
  335. base::Timer timer;
  336. Ui::Animations::Simple animation;
  337. float level = 0.;
  338. };
  339. const auto level = container->add(
  340. object_ptr<Ui::LevelMeter>(
  341. container,
  342. st::defaultLevelMeter),
  343. st::settingsLevelMeterPadding);
  344. const auto state = level->lifetime().make_state<LevelState>();
  345. level->resize(QSize(0, st::defaultLevelMeter.height));
  346. state->timer.setCallback([=] {
  347. const auto was = state->level;
  348. state->level = state->tester->getAndResetLevel();
  349. state->animation.start([=] {
  350. level->setValue(state->animation.value(state->level));
  351. }, was, state->level, kMicTestAnimationDuration);
  352. });
  353. _testingMicrophone.value() | rpl::start_with_next([=](bool testing) {
  354. if (testing) {
  355. state->deviceId = std::make_unique<Webrtc::DeviceResolver>(
  356. &Core::App().mediaDevices(),
  357. Webrtc::DeviceType::Capture,
  358. rpl::duplicate(resolvedId));
  359. state->tester = std::make_unique<AudioInputTester>(
  360. state->deviceId->value());
  361. state->timer.callEach(kMicTestUpdateInterval);
  362. } else {
  363. state->timer.cancel();
  364. state->animation.stop();
  365. state->tester = nullptr;
  366. state->deviceId = nullptr;
  367. }
  368. }, level->lifetime());
  369. }
  370. void Calls::requestPermissionAndStartTestingMicrophone() {
  371. using PermissionType = ::Platform::PermissionType;
  372. using PermissionStatus = ::Platform::PermissionStatus;
  373. const auto status = GetPermissionStatus(
  374. PermissionType::Microphone);
  375. if (status == PermissionStatus::Granted) {
  376. _testingMicrophone = true;
  377. } else if (status == PermissionStatus::CanRequest) {
  378. const auto startTestingChecked = crl::guard(this, [=](
  379. PermissionStatus status) {
  380. if (status == PermissionStatus::Granted) {
  381. crl::on_main(crl::guard(this, [=] {
  382. _testingMicrophone = true;
  383. }));
  384. }
  385. });
  386. RequestPermission(
  387. PermissionType::Microphone,
  388. startTestingChecked);
  389. } else {
  390. const auto showSystemSettings = [controller = _controller] {
  391. OpenSystemSettingsForPermission(
  392. PermissionType::Microphone);
  393. controller->hideLayer();
  394. };
  395. _controller->show(Ui::MakeConfirmBox({
  396. .text = tr::lng_no_mic_permission(),
  397. .confirmed = showSystemSettings,
  398. .confirmText = tr::lng_menu_settings(),
  399. }));
  400. }
  401. }
  402. rpl::producer<QString> PlaybackDeviceNameValue(rpl::producer<QString> id) {
  403. return DeviceNameValue(DeviceType::Playback, std::move(id));
  404. }
  405. rpl::producer<QString> CaptureDeviceNameValue(rpl::producer<QString> id) {
  406. return DeviceNameValue(DeviceType::Capture, std::move(id));
  407. }
  408. rpl::producer<QString> CameraDeviceNameValue(
  409. rpl::producer<QString> id) {
  410. return DeviceNameValue(DeviceType::Camera, std::move(id));
  411. }
  412. void ChooseMediaDeviceBox(
  413. not_null<Ui::GenericBox*> box,
  414. rpl::producer<QString> title,
  415. rpl::producer<std::vector<DeviceInfo>> devicesValue,
  416. rpl::producer<QString> currentId,
  417. Fn<void(QString id)> chosen,
  418. const style::Checkbox *st,
  419. const style::Radio *radioSt) {
  420. box->setTitle(std::move(title));
  421. box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
  422. const auto layout = box->verticalLayout();
  423. const auto skip = st::boxOptionListPadding.top()
  424. + st::defaultBoxCheckbox.margin.top();
  425. layout->add(object_ptr<Ui::FixedHeightWidget>(layout, skip));
  426. if (!st) {
  427. st = &st::defaultBoxCheckbox;
  428. }
  429. if (!radioSt) {
  430. radioSt = &st::defaultRadio;
  431. }
  432. struct State {
  433. std::vector<DeviceInfo> list;
  434. base::flat_map<int, QString> ids;
  435. rpl::variable<QString> currentId;
  436. QString currentName;
  437. bool ignoreValueChange = false;
  438. };
  439. const auto state = box->lifetime().make_state<State>();
  440. state->currentId = std::move(currentId);
  441. const auto choose = [=](const QString &id) {
  442. const auto weak = Ui::MakeWeak(box);
  443. chosen(id);
  444. if (weak) {
  445. box->closeBox();
  446. }
  447. };
  448. const auto group = std::make_shared<Ui::RadiobuttonGroup>();
  449. const auto fake = std::make_shared<Ui::RadiobuttonGroup>(0);
  450. const auto buttons = layout->add(object_ptr<Ui::VerticalLayout>(layout));
  451. const auto other = layout->add(object_ptr<Ui::VerticalLayout>(layout));
  452. const auto margins = QMargins(
  453. st::boxPadding.left() + st::boxOptionListPadding.left(),
  454. 0,
  455. st::boxPadding.right(),
  456. st::boxOptionListSkip);
  457. const auto def = buttons->add(
  458. object_ptr<Ui::Radiobutton>(
  459. buttons,
  460. group,
  461. 0,
  462. tr::lng_settings_call_device_default(tr::now),
  463. *st,
  464. *radioSt),
  465. margins);
  466. def->clicks(
  467. ) | rpl::filter([=] {
  468. return !group->value();
  469. }) | rpl::start_with_next([=] {
  470. choose(kDefaultDeviceId);
  471. }, def->lifetime());
  472. const auto showUnavailable = [=](QString text) {
  473. AddSkip(other);
  474. AddSubsectionTitle(other, tr::lng_settings_devices_inactive());
  475. const auto &radio = *radioSt;
  476. const auto button = other->add(
  477. object_ptr<Ui::Radiobutton>(other, fake, 0, text, *st, radio),
  478. margins);
  479. button->show();
  480. button->setDisabled(true);
  481. button->finishAnimating();
  482. button->setAttribute(Qt::WA_TransparentForMouseEvents);
  483. while (other->count() > 3) {
  484. delete other->widgetAt(0);
  485. }
  486. if (const auto width = box->width()) {
  487. other->resizeToWidth(width);
  488. }
  489. };
  490. const auto hideUnavailable = [=] {
  491. while (other->count() > 0) {
  492. delete other->widgetAt(0);
  493. }
  494. };
  495. const auto selectCurrent = [=](QString current) {
  496. state->ignoreValueChange = true;
  497. const auto guard = gsl::finally([&] {
  498. state->ignoreValueChange = false;
  499. });
  500. if (current.isEmpty() || current == kDefaultDeviceId) {
  501. group->setValue(0);
  502. hideUnavailable();
  503. } else {
  504. auto found = false;
  505. for (const auto &[index, id] : state->ids) {
  506. if (id == current) {
  507. group->setValue(index);
  508. found = true;
  509. break;
  510. }
  511. }
  512. if (found) {
  513. hideUnavailable();
  514. } else {
  515. group->setValue(0);
  516. const auto i = ranges::find(
  517. state->list,
  518. current,
  519. &DeviceInfo::id);
  520. if (i != end(state->list)) {
  521. showUnavailable(i->name);
  522. } else {
  523. hideUnavailable();
  524. }
  525. }
  526. }
  527. };
  528. std::move(
  529. devicesValue
  530. ) | rpl::start_with_next([=](std::vector<DeviceInfo> &&list) {
  531. auto count = buttons->count();
  532. auto index = 1;
  533. state->ids.clear();
  534. state->list = std::move(list);
  535. state->ignoreValueChange = true;
  536. const auto guard = gsl::finally([&] {
  537. state->ignoreValueChange = false;
  538. });
  539. const auto current = state->currentId.current();
  540. for (const auto &info : state->list) {
  541. const auto id = info.id;
  542. if (info.inactive) {
  543. continue;
  544. } else if (current == id) {
  545. group->setValue(index);
  546. }
  547. const auto button = buttons->insert(
  548. index,
  549. object_ptr<Ui::Radiobutton>(
  550. buttons,
  551. group,
  552. index,
  553. info.name,
  554. *st,
  555. *radioSt),
  556. margins);
  557. button->show();
  558. button->finishAnimating();
  559. button->clicks(
  560. ) | rpl::filter([=] {
  561. return (group->current() == index);
  562. }) | rpl::start_with_next([=] {
  563. choose(id);
  564. }, button->lifetime());
  565. state->ids.emplace(index, id);
  566. if (index < count) {
  567. delete buttons->widgetAt(index + 1);
  568. }
  569. ++index;
  570. }
  571. while (index < count) {
  572. delete buttons->widgetAt(index);
  573. --count;
  574. }
  575. if (const auto width = box->width()) {
  576. buttons->resizeToWidth(width);
  577. }
  578. selectCurrent(current);
  579. }, box->lifetime());
  580. state->currentId.changes(
  581. ) | rpl::start_with_next(selectCurrent, box->lifetime());
  582. def->finishAnimating();
  583. group->setChangedCallback([=](int value) {
  584. if (state->ignoreValueChange) {
  585. return;
  586. }
  587. const auto i = state->ids.find(value);
  588. choose((i != end(state->ids)) ? i->second : kDefaultDeviceId);
  589. });
  590. }
  591. object_ptr<Ui::GenericBox> ChoosePlaybackDeviceBox(
  592. rpl::producer<QString> currentId,
  593. Fn<void(QString id)> chosen,
  594. const style::Checkbox *st,
  595. const style::Radio *radioSt) {
  596. return Box(
  597. ChooseMediaDeviceBox,
  598. tr::lng_settings_call_output_device(),
  599. Core::App().mediaDevices().devicesValue(DeviceType::Playback),
  600. std::move(currentId),
  601. std::move(chosen),
  602. st,
  603. radioSt);
  604. }
  605. object_ptr<Ui::GenericBox> ChooseCaptureDeviceBox(
  606. rpl::producer<QString> currentId,
  607. Fn<void(QString id)> chosen,
  608. const style::Checkbox *st,
  609. const style::Radio *radioSt) {
  610. return Box(
  611. ChooseMediaDeviceBox,
  612. tr::lng_settings_call_input_device(),
  613. Core::App().mediaDevices().devicesValue(DeviceType::Capture),
  614. std::move(currentId),
  615. std::move(chosen),
  616. st,
  617. radioSt);
  618. }
  619. object_ptr<Ui::GenericBox> ChooseCameraDeviceBox(
  620. rpl::producer<QString> currentId,
  621. Fn<void(QString id)> chosen,
  622. const style::Checkbox *st,
  623. const style::Radio *radioSt) {
  624. return Box(
  625. ChooseMediaDeviceBox,
  626. tr::lng_settings_call_camera(),
  627. Core::App().mediaDevices().devicesValue(DeviceType::Camera),
  628. std::move(currentId),
  629. std::move(chosen),
  630. st,
  631. radioSt);
  632. }
  633. } // namespace Settings