VolumeControl.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  1. #include "VolumeControl.hpp"
  2. #include <components/MuteCheckBox.hpp>
  3. #include <components/VolumeMeter.hpp>
  4. #include <components/VolumeName.hpp>
  5. #include <components/VolumeSlider.hpp>
  6. #include <dialogs/NameDialog.hpp>
  7. #include <widgets/OBSBasic.hpp>
  8. #include <QMessageBox>
  9. #include <QObjectCleanupHandler>
  10. #include "moc_VolumeControl.cpp"
  11. namespace {
  12. bool isSourceUnassigned(obs_source_t *source)
  13. {
  14. uint32_t mixes = (obs_source_get_audio_mixers(source) & ((1 << MAX_AUDIO_MIXES) - 1));
  15. obs_monitoring_type mt = obs_source_get_monitoring_type(source);
  16. return mixes == 0 && mt != OBS_MONITORING_TYPE_MONITOR_ONLY;
  17. }
  18. void showUnassignedWarning(const char *name)
  19. {
  20. auto msgBox = [=]() {
  21. QMessageBox msgbox(App()->GetMainWindow());
  22. msgbox.setWindowTitle(QTStr("VolControl.UnassignedWarning.Title"));
  23. msgbox.setText(QTStr("VolControl.UnassignedWarning.Text").arg(name));
  24. msgbox.setIcon(QMessageBox::Icon::Information);
  25. msgbox.addButton(QMessageBox::Ok);
  26. QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain"));
  27. msgbox.setCheckBox(cb);
  28. msgbox.exec();
  29. if (cb->isChecked()) {
  30. config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources", true);
  31. config_save_safe(App()->GetUserConfig(), "tmp", nullptr);
  32. }
  33. };
  34. QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox));
  35. }
  36. } // namespace
  37. VolumeControl::VolumeControl(obs_source_t *source, QWidget *parent, bool vertical)
  38. : weakSource_(OBSGetWeakRef(source)),
  39. obs_fader(obs_fader_create(OBS_FADER_LOG)),
  40. vertical(vertical),
  41. contextMenu(nullptr),
  42. QFrame(parent)
  43. {
  44. setAttribute(Qt::WA_OpaquePaintEvent, true);
  45. utils = std::make_unique<idian::Utils>(this);
  46. uuid = obs_source_get_uuid(source);
  47. mainLayout = new QBoxLayout(QBoxLayout::LeftToRight, this);
  48. mainLayout->setContentsMargins(0, 0, 0, 0);
  49. mainLayout->setSpacing(0);
  50. setLayout(mainLayout);
  51. categoryLabel = new QLabel("Active");
  52. categoryLabel->setAlignment(Qt::AlignCenter);
  53. utils->addClass(categoryLabel, "mixer-category");
  54. utils->addClass(categoryLabel, "text-tiny");
  55. nameButton = new VolumeName(source, this);
  56. nameButton->setMaximumWidth(140);
  57. utils->addClass(nameButton, "text-small");
  58. utils->addClass(nameButton, "mixer-name");
  59. muteButton = new QPushButton(this);
  60. muteButton->setCheckable(true);
  61. utils->addClass(muteButton, "btn-mute");
  62. monitorButton = new QPushButton(this);
  63. monitorButton->setCheckable(true);
  64. utils->addClass(monitorButton, "btn-monitor");
  65. volumeLabel = new QLabel(this);
  66. volumeLabel->setObjectName("volLabel");
  67. slider = new VolumeSlider(obs_fader, Qt::Horizontal, this);
  68. slider->setMinimum(0);
  69. slider->setMaximum(int(FADER_PRECISION));
  70. sourceName = obs_source_get_name(source);
  71. setObjectName(sourceName);
  72. utils->applyStateStylingEventFilter(muteButton);
  73. utils->applyStateStylingEventFilter(monitorButton);
  74. volumeMeter = new VolumeMeter(this, source);
  75. bool muted = obs_source_muted(source);
  76. bool unassigned = isSourceUnassigned(source);
  77. volumeMeter->setMuted(muted || unassigned);
  78. setLayoutVertical(vertical);
  79. setName(sourceName);
  80. obs_fader_add_callback(obs_fader, obsVolumeChanged, this);
  81. obsSignals.reserve(9);
  82. obsSignals.emplace_back(obs_source_get_signal_handler(source), "mute", obsVolumeMuted, this);
  83. obsSignals.emplace_back(obs_source_get_signal_handler(source), "audio_mixers", obsMixersOrMonitoringChanged,
  84. this);
  85. obsSignals.emplace_back(obs_source_get_signal_handler(source), "audio_monitoring", obsMixersOrMonitoringChanged,
  86. this);
  87. obsSignals.emplace_back(obs_source_get_signal_handler(source), "activate", VolumeControl::obsSourceActivated,
  88. this);
  89. obsSignals.emplace_back(obs_source_get_signal_handler(source), "deactivate",
  90. VolumeControl::obsSourceDeactivated, this);
  91. obsSignals.emplace_back(obs_source_get_signal_handler(source), "audio_activate",
  92. VolumeControl::obsSourceActivated, this);
  93. obsSignals.emplace_back(obs_source_get_signal_handler(source), "audio_deactivate",
  94. VolumeControl::obsSourceDeactivated, this);
  95. obsSignals.emplace_back(obs_source_get_signal_handler(source), "remove", VolumeControl::obsSourceDestroy, this);
  96. obsSignals.emplace_back(obs_source_get_signal_handler(source), "destroy", VolumeControl::obsSourceDestroy,
  97. this);
  98. setContextMenuPolicy(Qt::CustomContextMenu);
  99. connect(this, &QWidget::customContextMenuRequested, this, &VolumeControl::showVolumeControlMenu);
  100. connect(nameButton, &VolumeName::renamed, this, &VolumeControl::setName);
  101. connect(nameButton, &VolumeName::clicked, this, [&]() { showVolumeControlMenu(); });
  102. connect(slider, &VolumeSlider::valueChanged, this, &VolumeControl::sliderChanged);
  103. connect(muteButton, &QPushButton::clicked, this, &VolumeControl::handleMuteButton);
  104. connect(monitorButton, &QPushButton::clicked, this, &VolumeControl::handleMonitorButton);
  105. OBSBasic *main = OBSBasic::Get();
  106. if (main) {
  107. connect(main, &OBSBasic::profileSettingChanged, this,
  108. [this](const std::string &category, const std::string &name) {
  109. if (category == "Audio" && name == "MeterDecayRate") {
  110. updateDecayRate();
  111. } else if (category == "Audio" && name == "PeakMeterType") {
  112. updatePeakMeterType();
  113. }
  114. });
  115. }
  116. obs_fader_attach_source(obs_fader, source);
  117. // Call volume changed once to init the slider position and label
  118. changeVolume();
  119. updateMixerState();
  120. }
  121. VolumeControl::~VolumeControl()
  122. {
  123. obs_fader_remove_callback(obs_fader, obsVolumeChanged, this);
  124. obsSignals.clear();
  125. if (contextMenu) {
  126. contextMenu->close();
  127. }
  128. }
  129. void VolumeControl::obsVolumeChanged(void *data, float)
  130. {
  131. VolumeControl *volControl = static_cast<VolumeControl *>(data);
  132. QMetaObject::invokeMethod(volControl, "changeVolume", Qt::QueuedConnection);
  133. }
  134. void VolumeControl::obsVolumeMuted(void *data, calldata_t *)
  135. {
  136. VolumeControl *volControl = static_cast<VolumeControl *>(data);
  137. QMetaObject::invokeMethod(volControl, "updateMixerState", Qt::QueuedConnection);
  138. }
  139. void VolumeControl::obsMixersOrMonitoringChanged(void *data, calldata_t *)
  140. {
  141. VolumeControl *volControl = static_cast<VolumeControl *>(data);
  142. QMetaObject::invokeMethod(volControl, "updateMixerState", Qt::QueuedConnection);
  143. }
  144. void VolumeControl::obsSourceActivated(void *data, calldata_t *)
  145. {
  146. QMetaObject::invokeMethod(static_cast<VolumeControl *>(data), "sourceActiveChanged", Qt::QueuedConnection,
  147. Q_ARG(bool, true));
  148. }
  149. void VolumeControl::obsSourceDeactivated(void *data, calldata_t *)
  150. {
  151. QMetaObject::invokeMethod(static_cast<VolumeControl *>(data), "sourceActiveChanged", Qt::QueuedConnection,
  152. Q_ARG(bool, false));
  153. }
  154. void VolumeControl::obsSourceDestroy(void *data, calldata_t *)
  155. {
  156. QMetaObject::invokeMethod(static_cast<VolumeControl *>(data), "handleSourceDestroyed", Qt::QueuedConnection);
  157. }
  158. void VolumeControl::setLayoutVertical(bool vertical)
  159. {
  160. QBoxLayout *newLayout = new QBoxLayout(vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
  161. newLayout->setContentsMargins(0, 0, 0, 0);
  162. newLayout->setSpacing(0);
  163. if (vertical) {
  164. setMaximumWidth(110);
  165. setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
  166. QHBoxLayout *categoryLayout = new QHBoxLayout;
  167. QHBoxLayout *nameLayout = new QHBoxLayout;
  168. QHBoxLayout *controlLayout = new QHBoxLayout;
  169. QHBoxLayout *volLayout = new QHBoxLayout;
  170. QFrame *meterFrame = new QFrame;
  171. QHBoxLayout *meterLayout = new QHBoxLayout;
  172. volumeMeter->setVertical(true);
  173. volumeMeter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
  174. slider->setOrientation(Qt::Vertical);
  175. slider->setLayoutDirection(Qt::LeftToRight);
  176. slider->setDisplayTicks(true);
  177. nameButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
  178. categoryLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
  179. volumeLabel->setAlignment(Qt::AlignLeft);
  180. categoryLayout->setAlignment(Qt::AlignCenter);
  181. nameLayout->setAlignment(Qt::AlignCenter);
  182. meterLayout->setAlignment(Qt::AlignCenter);
  183. controlLayout->setAlignment(Qt::AlignCenter);
  184. volLayout->setAlignment(Qt::AlignCenter);
  185. meterFrame->setObjectName("volMeterFrame");
  186. categoryLayout->setContentsMargins(0, 0, 0, 0);
  187. categoryLayout->setSpacing(0);
  188. categoryLayout->addWidget(categoryLabel);
  189. nameLayout->setContentsMargins(0, 0, 0, 0);
  190. nameLayout->setSpacing(0);
  191. nameLayout->addWidget(nameButton);
  192. controlLayout->setContentsMargins(0, 0, 0, 0);
  193. controlLayout->setSpacing(0);
  194. // Add Headphone (audio monitoring) widget here
  195. controlLayout->addWidget(muteButton);
  196. controlLayout->addWidget(monitorButton);
  197. meterLayout->setContentsMargins(0, 0, 0, 0);
  198. meterLayout->setSpacing(0);
  199. meterLayout->addWidget(slider);
  200. meterLayout->addWidget(volumeMeter);
  201. meterFrame->setLayout(meterLayout);
  202. volLayout->setContentsMargins(0, 0, 0, 0);
  203. volLayout->setSpacing(0);
  204. volLayout->addWidget(volumeLabel);
  205. volLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Maximum));
  206. newLayout->addItem(categoryLayout);
  207. newLayout->addItem(nameLayout);
  208. newLayout->addItem(volLayout);
  209. newLayout->addWidget(meterFrame);
  210. newLayout->addItem(controlLayout);
  211. newLayout->setStretch(0, 0);
  212. newLayout->setStretch(1, 0);
  213. newLayout->setStretch(2, 0);
  214. newLayout->setStretch(3, 1);
  215. newLayout->setStretch(4, 0);
  216. volumeMeter->setFocusProxy(slider);
  217. } else {
  218. setMaximumWidth(QWIDGETSIZE_MAX);
  219. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
  220. QVBoxLayout *textLayout = new QVBoxLayout;
  221. QHBoxLayout *controlLayout = new QHBoxLayout;
  222. QFrame *meterFrame = new QFrame;
  223. QVBoxLayout *meterLayout = new QVBoxLayout;
  224. QVBoxLayout *buttonLayout = new QVBoxLayout;
  225. volumeMeter->setVertical(false);
  226. volumeMeter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
  227. slider->setOrientation(Qt::Horizontal);
  228. slider->setLayoutDirection(Qt::LeftToRight);
  229. slider->setDisplayTicks(true);
  230. nameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
  231. categoryLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
  232. volumeLabel->setAlignment(Qt::AlignRight);
  233. QHBoxLayout *textTopLayout = new QHBoxLayout;
  234. textTopLayout->setContentsMargins(0, 0, 0, 0);
  235. textTopLayout->addWidget(categoryLabel);
  236. textTopLayout->addWidget(volumeLabel);
  237. textLayout->setContentsMargins(0, 0, 0, 0);
  238. textLayout->addItem(textTopLayout);
  239. textLayout->addWidget(nameButton);
  240. meterFrame->setObjectName("volMeterFrame");
  241. meterFrame->setLayout(meterLayout);
  242. meterLayout->setContentsMargins(0, 0, 0, 0);
  243. meterLayout->setSpacing(0);
  244. meterLayout->addWidget(slider);
  245. meterLayout->addWidget(volumeMeter);
  246. meterLayout->setStretch(0, 2);
  247. meterLayout->setStretch(1, 2);
  248. buttonLayout->setContentsMargins(0, 0, 0, 0);
  249. buttonLayout->setSpacing(0);
  250. buttonLayout->addWidget(muteButton);
  251. buttonLayout->addWidget(monitorButton);
  252. controlLayout->addItem(buttonLayout);
  253. controlLayout->addWidget(meterFrame);
  254. newLayout->addItem(textLayout);
  255. newLayout->addItem(controlLayout);
  256. newLayout->setStretch(0, 3);
  257. newLayout->setStretch(1, 6);
  258. volumeMeter->setFocusProxy(slider);
  259. }
  260. QWidget().setLayout(mainLayout);
  261. setLayout(newLayout);
  262. mainLayout = newLayout;
  263. updateTabOrder();
  264. adjustSize();
  265. }
  266. void VolumeControl::showVolumeControlMenu(QPoint pos)
  267. {
  268. OBSSource source = OBSGetStrongRef(weakSource());
  269. if (!source) {
  270. return;
  271. }
  272. QMenu *popup = new QMenu(this);
  273. // Create menu QActions
  274. QAction *lockAction = new QAction(QTStr("LockVolume"), popup);
  275. lockAction->setCheckable(true);
  276. lockAction->setChecked(mixerStatus().has(VolumeControl::MixerStatus::Locked));
  277. bool isGlobal = mixerStatus().has(VolumeControl::MixerStatus::Global);
  278. QAction *pinAction = new QAction(QTStr("Basic.AudioMixer.Pin"), popup);
  279. bool isPinned = mixerStatus().has(VolumeControl::MixerStatus::Pinned);
  280. if (isPinned) {
  281. pinAction->setText(QTStr("Basic.AudioMixer.Unpin"));
  282. }
  283. QAction *hideAction = new QAction(QTStr("Basic.AudioMixer.Hide"), popup);
  284. bool isHidden = mixerStatus().has(VolumeControl::MixerStatus::Hidden);
  285. if (isHidden && !isGlobal) {
  286. hideAction->setText(QTStr("Basic.AudioMixer.Unhide"));
  287. }
  288. QAction *unhideAllAction = new QAction(QTStr("UnhideAll"), popup);
  289. QAction *mixerRenameAction = new QAction(QTStr("Rename"), popup);
  290. QAction *copyFiltersAction = new QAction(QTStr("Copy.Filters"), popup);
  291. QAction *pasteFiltersAction = new QAction(QTStr("Paste.Filters"), popup);
  292. QAction *filtersAction = new QAction(QTStr("Filters"), popup);
  293. QAction *propertiesAction = new QAction(QTStr("Properties"), popup);
  294. // Set properties on actions that require source reference
  295. hideAction->setProperty("source", QVariant::fromValue<OBSSource>(source));
  296. pinAction->setProperty("source", QVariant::fromValue<OBSSource>(source));
  297. mixerRenameAction->setProperty("source", QVariant::fromValue<OBSSource>(source));
  298. copyFiltersAction->setProperty("source", QVariant::fromValue<OBSSource>(source));
  299. pasteFiltersAction->setProperty("source", QVariant::fromValue<OBSSource>(source));
  300. filtersAction->setProperty("source", QVariant::fromValue<OBSSource>(source));
  301. propertiesAction->setProperty("source", QVariant::fromValue<OBSSource>(source));
  302. // Connect actions to signals
  303. OBSBasic *main = OBSBasic::Get();
  304. connect(unhideAllAction, &QAction::triggered, this, [this]() { emit unhideAll(); });
  305. connect(hideAction, &QAction::triggered, this, [this, isHidden]() { setHiddenInMixer(!isHidden); });
  306. connect(
  307. pinAction, &QAction::triggered, this, [this, isPinned]() { setPinnedInMixer(!isPinned); },
  308. Qt::DirectConnection);
  309. connect(lockAction, &QAction::toggled, this, &VolumeControl::setLocked);
  310. connect(copyFiltersAction, &QAction::triggered, main, &OBSBasic::actionCopyFilters);
  311. connect(pasteFiltersAction, &QAction::triggered, main, &OBSBasic::actionPasteFilters);
  312. connect(mixerRenameAction, &QAction::triggered, this, &VolumeControl::renameSource);
  313. connect(filtersAction, &QAction::triggered, main, &OBSBasic::actionOpenSourceFilters);
  314. connect(propertiesAction, &QAction::triggered, main, &OBSBasic::actionOpenSourceProperties);
  315. // Enable/disable actions
  316. copyFiltersAction->setEnabled(obs_source_filter_count(source) > 0);
  317. pasteFiltersAction->setEnabled(!obs_weak_source_expired(main->copyFiltersSource()));
  318. if (isGlobal) {
  319. pinAction->setDisabled(true);
  320. hideAction->setDisabled(true);
  321. }
  322. if (isPinned) {
  323. hideAction->setDisabled(true);
  324. }
  325. // Build menu
  326. popup->addAction(unhideAllAction);
  327. popup->addSeparator();
  328. popup->addAction(pinAction);
  329. popup->addAction(hideAction);
  330. popup->addAction(lockAction);
  331. popup->addSeparator();
  332. popup->addAction(copyFiltersAction);
  333. popup->addAction(pasteFiltersAction);
  334. popup->addSeparator();
  335. popup->addAction(mixerRenameAction);
  336. popup->addSeparator();
  337. popup->addAction(filtersAction);
  338. popup->addAction(propertiesAction);
  339. // Calculate menu position
  340. QPoint popupPos = mapToGlobal(pos);
  341. if (pos.isNull()) {
  342. QPoint menuPos = nameButton->mapToGlobal(nameButton->rect().bottomLeft());
  343. QSize menuSize = popup->sizeHint();
  344. QRect available = QGuiApplication::screenAt(menuPos)->availableGeometry();
  345. int spaceBelow = available.bottom() - menuPos.y();
  346. int spaceAbove = menuPos.y() - available.top();
  347. if (menuSize.height() > spaceBelow && spaceAbove > spaceBelow) {
  348. menuPos = nameButton->mapToGlobal(nameButton->rect().topLeft());
  349. menuPos.ry() -= menuSize.height();
  350. }
  351. if (menuPos.x() + menuSize.width() > available.right()) {
  352. menuPos.rx() = available.right() - menuSize.width();
  353. }
  354. popupPos = menuPos;
  355. }
  356. popup->popup(popupPos);
  357. connect(popup, &QMenu::aboutToHide, popup, &QMenu::deleteLater);
  358. }
  359. void VolumeControl::renameSource()
  360. {
  361. QAction *action = reinterpret_cast<QAction *>(sender());
  362. obs_source_t *source = action->property("source").value<OBSSource>();
  363. const char *prevName = obs_source_get_name(source);
  364. for (;;) {
  365. std::string name;
  366. bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"),
  367. QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName));
  368. if (!accepted) {
  369. return;
  370. }
  371. if (name.empty()) {
  372. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  373. continue;
  374. }
  375. OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str());
  376. if (sourceTest) {
  377. OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  378. continue;
  379. }
  380. std::string prevName(obs_source_get_name(source));
  381. auto undo = [prevName](const std::string &data) {
  382. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  383. obs_source_set_name(source, prevName.c_str());
  384. };
  385. std::string editedName = name;
  386. auto redo = [editedName](const std::string &data) {
  387. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  388. obs_source_set_name(source, editedName.c_str());
  389. };
  390. OBSBasic *main = OBSBasic::Get();
  391. const char *uuid = obs_source_get_uuid(source);
  392. main->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, uuid, uuid);
  393. obs_source_set_name(source, name.c_str());
  394. break;
  395. }
  396. }
  397. void VolumeControl::changeVolume()
  398. {
  399. QSignalBlocker blocker(slider);
  400. slider->setValue((int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION));
  401. updateText();
  402. }
  403. void VolumeControl::setLocked(bool locked)
  404. {
  405. OBSSource source = OBSGetStrongRef(weakSource());
  406. if (!source) {
  407. return;
  408. }
  409. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  410. obs_data_set_bool(priv_settings, "volume_locked", locked);
  411. enableSlider(!locked);
  412. mixerStatus().set(VolumeControl::MixerStatus::Locked, locked);
  413. OBSBasic *main = OBSBasic::Get();
  414. emit main->mixerStatusChanged(uuid);
  415. }
  416. void VolumeControl::updateCategoryLabel()
  417. {
  418. QString labelText = QTStr("Basic.AudioMixer.Category.Active");
  419. if (mixerStatus().has(VolumeControl::MixerStatus::Unassigned)) {
  420. labelText = QTStr("Basic.AudioMixer.Category.Unassigned");
  421. } else if (mixerStatus().has(VolumeControl::MixerStatus::Global)) {
  422. labelText = QTStr("Basic.AudioMixer.Category.Global");
  423. } else if (mixerStatus().has(VolumeControl::MixerStatus::Pinned)) {
  424. labelText = QTStr("Basic.AudioMixer.Category.Pinned");
  425. } else if (mixerStatus().has(VolumeControl::MixerStatus::Hidden)) {
  426. labelText = QTStr("Basic.AudioMixer.Category.Hidden");
  427. } else if (!mixerStatus().has(VolumeControl::MixerStatus::Active)) {
  428. labelText = QTStr("Basic.AudioMixer.Category.Inactive");
  429. if (mixerStatus().has(VolumeControl::MixerStatus::Preview)) {
  430. labelText = QTStr("Basic.AudioMixer.Category.Preview");
  431. }
  432. }
  433. bool stylePinned = mixerStatus().has(VolumeControl::MixerStatus::Global) ||
  434. mixerStatus().has(VolumeControl::MixerStatus::Pinned);
  435. bool styleInactive = mixerStatus().has(VolumeControl::MixerStatus::Active) != true;
  436. bool styleHidden = mixerStatus().has(VolumeControl::MixerStatus::Hidden);
  437. bool styleUnassigned = mixerStatus().has(VolumeControl::MixerStatus::Unassigned);
  438. bool stylePreviewed = mixerStatus().has(VolumeControl::MixerStatus::Preview);
  439. utils->toggleClass("volume-pinned", stylePinned);
  440. utils->toggleClass("volume-inactive", styleInactive);
  441. utils->toggleClass("volume-preview", styleInactive && stylePreviewed);
  442. utils->toggleClass("volume-hidden", styleHidden && !stylePinned);
  443. utils->toggleClass("volume-unassigned", styleUnassigned);
  444. categoryLabel->setText(labelText);
  445. utils->polishChildren();
  446. volumeMeter->updateBackgroundCache();
  447. }
  448. void VolumeControl::updateDecayRate()
  449. {
  450. OBSBasic *main = OBSBasic::Get();
  451. double meterDecayRate = config_get_double(main->Config(), "Audio", "MeterDecayRate");
  452. setMeterDecayRate(meterDecayRate);
  453. }
  454. void VolumeControl::updatePeakMeterType()
  455. {
  456. OBSBasic *main = OBSBasic::Get();
  457. uint32_t peakMeterTypeIdx = config_get_uint(main->Config(), "Audio", "PeakMeterType");
  458. enum obs_peak_meter_type peakMeterType;
  459. switch (peakMeterTypeIdx) {
  460. case 0:
  461. peakMeterType = SAMPLE_PEAK_METER;
  462. break;
  463. case 1:
  464. peakMeterType = TRUE_PEAK_METER;
  465. break;
  466. default:
  467. peakMeterType = SAMPLE_PEAK_METER;
  468. break;
  469. }
  470. setPeakMeterType(peakMeterType);
  471. }
  472. void VolumeControl::setMuted(bool mute)
  473. {
  474. OBSSource source = OBSGetStrongRef(weakSource());
  475. if (!source) {
  476. return;
  477. }
  478. bool prev = obs_source_muted(source);
  479. bool unassigned = isSourceUnassigned(source);
  480. obs_source_set_muted(source, mute);
  481. if (!mute && unassigned) {
  482. // Show notice about the source no being assigned to any tracks
  483. bool has_shown_warning =
  484. config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources");
  485. if (!has_shown_warning) {
  486. showUnassignedWarning(obs_source_get_name(source));
  487. }
  488. }
  489. auto undo_redo = [](const std::string &uuid, bool val) {
  490. OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str());
  491. obs_source_set_muted(source, val);
  492. };
  493. QString text = QTStr(mute ? "Undo.Volume.Mute" : "Undo.Volume.Unmute");
  494. const char *name = obs_source_get_name(source);
  495. OBSBasic::Get()->undo_s.add_action(text.arg(name), std::bind(undo_redo, std::placeholders::_1, prev),
  496. std::bind(undo_redo, std::placeholders::_1, mute), uuid, uuid);
  497. }
  498. void VolumeControl::setMonitoring(obs_monitoring_type type)
  499. {
  500. OBSSource source = OBSGetStrongRef(weakSource());
  501. if (!source) {
  502. return;
  503. }
  504. obs_monitoring_type prevMonitoringType = obs_source_get_monitoring_type(source);
  505. obs_source_set_monitoring_type(source, type);
  506. auto undo_redo = [](const std::string &uuid, obs_monitoring_type val) {
  507. OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str());
  508. obs_source_set_monitoring_type(source, val);
  509. };
  510. QString text = QTStr("Undo.MonitoringType.Change");
  511. const char *name = obs_source_get_name(source);
  512. OBSBasic::Get()->undo_s.add_action(text.arg(name),
  513. std::bind(undo_redo, std::placeholders::_1, prevMonitoringType),
  514. std::bind(undo_redo, std::placeholders::_1, type), uuid, uuid);
  515. }
  516. void VolumeControl::sourceActiveChanged(bool active)
  517. {
  518. setUseDisabledColors(!active);
  519. mixerStatus().set(VolumeControl::MixerStatus::Active, active);
  520. OBSBasic *main = OBSBasic::Get();
  521. emit main->mixerStatusChanged(uuid);
  522. }
  523. void VolumeControl::updateMixerState()
  524. {
  525. OBSSource source = OBSGetStrongRef(weakSource());
  526. if (!source) {
  527. deleteLater();
  528. return;
  529. }
  530. bool muted = obs_source_muted(source);
  531. bool unassigned = isSourceUnassigned(source);
  532. obs_monitoring_type monitoringType = obs_source_get_monitoring_type(source);
  533. bool isActive = obs_source_active(source) && obs_source_audio_active(source);
  534. mixerStatus().set(VolumeControl::MixerStatus::Active, isActive);
  535. setUseDisabledColors(!isActive);
  536. mixerStatus().set(VolumeControl::MixerStatus::Unassigned, unassigned);
  537. QSignalBlocker blockMute(muteButton);
  538. QSignalBlocker blockMonitor(monitorButton);
  539. bool showAsMuted = muted || monitoringType == OBS_MONITORING_TYPE_MONITOR_ONLY;
  540. bool showAsMonitored = monitoringType != OBS_MONITORING_TYPE_NONE;
  541. bool showAsUnassigned = !muted && unassigned;
  542. volumeMeter->setMuted((showAsMuted || showAsUnassigned) && !showAsMonitored);
  543. // Qt doesn't support overriding the QPushButton icon using pseudo state selectors like :checked
  544. // in QSS so we set a checked class selector on the button to be used instead.
  545. utils->toggleClass(muteButton, "checked", showAsMuted);
  546. utils->toggleClass(monitorButton, "checked", showAsMonitored);
  547. utils->toggleClass(muteButton, "mute-unassigned", showAsUnassigned);
  548. muteButton->setChecked(showAsMuted);
  549. monitorButton->setChecked(showAsMonitored);
  550. if (showAsUnassigned) {
  551. QIcon unassignedIcon;
  552. unassignedIcon.addFile(QString::fromUtf8(":/res/images/unassigned.svg"), QSize(16, 16),
  553. QIcon::Mode::Normal, QIcon::State::Off);
  554. muteButton->setIcon(unassignedIcon);
  555. } else if (showAsMuted) {
  556. QIcon mutedIcon;
  557. mutedIcon.addFile(QString::fromUtf8(":/res/images/mute.svg"), QSize(16, 16), QIcon::Mode::Normal,
  558. QIcon::State::Off);
  559. muteButton->setIcon(mutedIcon);
  560. } else {
  561. QIcon unmutedIcon;
  562. unmutedIcon.addFile(QString::fromUtf8(":/settings/images/settings/audio.svg"), QSize(16, 16),
  563. QIcon::Mode::Normal, QIcon::State::Off);
  564. muteButton->setIcon(unmutedIcon);
  565. }
  566. if (showAsMonitored) {
  567. QIcon monitorOnIcon;
  568. monitorOnIcon.addFile(QString::fromUtf8(":/res/images/headphones.svg"), QSize(16, 16),
  569. QIcon::Mode::Normal, QIcon::State::Off);
  570. monitorButton->setIcon(monitorOnIcon);
  571. } else {
  572. QIcon monitorOffIcon;
  573. monitorOffIcon.addFile(QString::fromUtf8(":/res/images/headphones-off.svg"), QSize(16, 16),
  574. QIcon::Mode::Normal, QIcon::State::Off);
  575. monitorButton->setIcon(monitorOffIcon);
  576. }
  577. utils->repolish(muteButton);
  578. utils->repolish(monitorButton);
  579. updateCategoryLabel();
  580. }
  581. void VolumeControl::handleMuteButton(bool mute)
  582. {
  583. OBSSource source = OBSGetStrongRef(weakSource());
  584. if (!source) {
  585. return;
  586. }
  587. // The Mute and Monitor buttons in the volume mixer work as a pseudo quad-state toggle.
  588. // Both buttons must be in their "off" state in order to actually process it as a mute.
  589. // Otherwise, clicking "Mute" with monitoring enabled will toggle the monitoring type.
  590. obs_monitoring_type monitoringType = obs_source_get_monitoring_type(source);
  591. if (mute && monitoringType == OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT) {
  592. setMonitoring(OBS_MONITORING_TYPE_MONITOR_ONLY);
  593. } else if (!mute && monitoringType == OBS_MONITORING_TYPE_MONITOR_ONLY) {
  594. setMonitoring(OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT);
  595. } else {
  596. setMuted(mute);
  597. }
  598. }
  599. void VolumeControl::handleMonitorButton(bool enableMonitoring)
  600. {
  601. OBSSource source = OBSGetStrongRef(weakSource());
  602. if (!source) {
  603. return;
  604. }
  605. // The Mute and Monitor buttons in the volume mixer work as a pseudo quad-state toggle.
  606. // The source is only ever actually "Muted" if Monitoring is set to None.
  607. obs_monitoring_type monitoringType = obs_source_get_monitoring_type(source);
  608. bool muted = obs_source_muted(source);
  609. if (!enableMonitoring) {
  610. setMonitoring(OBS_MONITORING_TYPE_NONE);
  611. if (monitoringType == OBS_MONITORING_TYPE_MONITOR_ONLY) {
  612. setMuted(true);
  613. }
  614. } else if (enableMonitoring && muted) {
  615. setMonitoring(OBS_MONITORING_TYPE_MONITOR_ONLY);
  616. setMuted(false);
  617. } else if (enableMonitoring && !muted) {
  618. setMonitoring(OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT);
  619. }
  620. }
  621. void VolumeControl::sliderChanged(int vol)
  622. {
  623. OBSSource source = OBSGetStrongRef(weakSource());
  624. if (!source) {
  625. return;
  626. }
  627. float prev = obs_source_get_volume(source);
  628. obs_fader_set_deflection(obs_fader, float(vol) / FADER_PRECISION);
  629. updateText();
  630. auto undo_redo = [](const std::string &uuid, float val) {
  631. OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str());
  632. obs_source_set_volume(source, val);
  633. };
  634. float val = obs_source_get_volume(source);
  635. const char *name = obs_source_get_name(source);
  636. OBSBasic::Get()->undo_s.add_action(QTStr("Undo.Volume.Change").arg(name),
  637. std::bind(undo_redo, std::placeholders::_1, prev),
  638. std::bind(undo_redo, std::placeholders::_1, val), uuid, uuid, true);
  639. }
  640. void VolumeControl::updateText()
  641. {
  642. QString text;
  643. float db = obs_fader_get_db(obs_fader);
  644. if (db < -96.0f) {
  645. text = "-inf dB";
  646. } else {
  647. text = QString::number(db, 'f', 1).append(" dB");
  648. }
  649. volumeLabel->setText(text);
  650. OBSSource source = OBSGetStrongRef(weakSource());
  651. if (!source) {
  652. return;
  653. }
  654. bool muted = obs_source_muted(source);
  655. const char *accTextLookup = muted ? "VolControl.SliderMuted" : "VolControl.SliderUnmuted";
  656. QString sourceName = obs_source_get_name(source);
  657. QString accText = QTStr(accTextLookup).arg(sourceName);
  658. slider->setAccessibleName(accText);
  659. }
  660. void VolumeControl::setVertical(bool vertical_)
  661. {
  662. if (vertical == vertical_) {
  663. return;
  664. }
  665. vertical = vertical_;
  666. setLayoutVertical(vertical);
  667. }
  668. void VolumeControl::updateTabOrder()
  669. {
  670. QWidget *prevFocus = firstWidget()->previousInFocusChain();
  671. QWidget *lastFocus = lastWidget()->nextInFocusChain();
  672. if (vertical) {
  673. setTabOrder(prevFocus, nameButton);
  674. setTabOrder(nameButton, slider);
  675. setTabOrder(slider, muteButton);
  676. setTabOrder(muteButton, monitorButton);
  677. setTabOrder(monitorButton, lastFocus);
  678. } else {
  679. setTabOrder(prevFocus, nameButton);
  680. setTabOrder(nameButton, muteButton);
  681. setTabOrder(muteButton, monitorButton);
  682. setTabOrder(monitorButton, slider);
  683. setTabOrder(slider, lastFocus);
  684. }
  685. }
  686. void VolumeControl::updateName()
  687. {
  688. setName(sourceName);
  689. }
  690. void VolumeControl::setName(QString name)
  691. {
  692. sourceName = name;
  693. muteButton->setAccessibleName(QTStr("VolControl.Mute").arg(name));
  694. }
  695. void VolumeControl::setMeterDecayRate(qreal q)
  696. {
  697. volumeMeter->setPeakDecayRate(q);
  698. }
  699. void VolumeControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
  700. {
  701. volumeMeter->setPeakMeterType(peakMeterType);
  702. }
  703. void VolumeControl::enableSlider(bool enable)
  704. {
  705. slider->setEnabled(enable);
  706. }
  707. void VolumeControl::setUseDisabledColors(bool greyscale)
  708. {
  709. volumeMeter->setUseDisabledColors(greyscale);
  710. }
  711. void VolumeControl::setGlobalInMixer(bool global)
  712. {
  713. if (mixerStatus().has(VolumeControl::MixerStatus::Global) != global) {
  714. mixerStatus().set(VolumeControl::MixerStatus::Global, global);
  715. OBSBasic *main = OBSBasic::Get();
  716. emit main->mixerStatusChanged(uuid);
  717. }
  718. }
  719. void VolumeControl::setPinnedInMixer(bool pinned)
  720. {
  721. if (mixerStatus().has(VolumeControl::MixerStatus::Pinned) != pinned) {
  722. OBSSource source = OBSGetStrongRef(weakSource());
  723. if (!source) {
  724. return;
  725. }
  726. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  727. obs_data_set_bool(priv_settings, "mixer_pinned", pinned);
  728. mixerStatus().set(VolumeControl::MixerStatus::Pinned, pinned);
  729. if (pinned) {
  730. // Unset hidden state when pinning controls
  731. setHiddenInMixer(false);
  732. }
  733. OBSBasic *main = OBSBasic::Get();
  734. emit main->mixerStatusChanged(uuid);
  735. }
  736. }
  737. void VolumeControl::setHiddenInMixer(bool hidden)
  738. {
  739. if (mixerStatus().has(VolumeControl::MixerStatus::Hidden) != hidden) {
  740. OBSSource source = OBSGetStrongRef(weakSource());
  741. if (!source) {
  742. return;
  743. }
  744. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  745. obs_data_set_bool(priv_settings, "mixer_hidden", hidden);
  746. mixerStatus().set(VolumeControl::MixerStatus::Hidden, hidden);
  747. OBSBasic *main = OBSBasic::Get();
  748. emit main->mixerStatusChanged(uuid);
  749. }
  750. }
  751. void VolumeControl::refreshColors()
  752. {
  753. volumeMeter->refreshColors();
  754. }
  755. void VolumeControl::setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS],
  756. const float inputPeak[MAX_AUDIO_CHANNELS])
  757. {
  758. if (volumeMeter) {
  759. volumeMeter->setLevels(magnitude, peak, inputPeak);
  760. }
  761. }