From df446c3f6e0b8562de6b2b28f127252984ec5679 Mon Sep 17 00:00:00 2001 From: Chip Bradford Date: Tue, 19 Jul 2022 09:32:39 -0700 Subject: [PATCH] UI: Add Virtual Camera source selector dialog --- UI/CMakeLists.txt | 3 + UI/data/locale/en-US.ini | 11 ++ UI/forms/OBSBasicVCamConfig.ui | 113 ++++++++++++ UI/record-button.cpp | 142 +++++++++++++- UI/record-button.hpp | 28 ++- UI/window-basic-main-outputs.cpp | 11 +- UI/window-basic-main-transitions.cpp | 4 + UI/window-basic-main.cpp | 114 +++++------- UI/window-basic-main.hpp | 9 +- UI/window-basic-vcam-config.cpp | 264 +++++++++++++++++++++++++++ UI/window-basic-vcam-config.hpp | 27 +++ 11 files changed, 638 insertions(+), 88 deletions(-) create mode 100644 UI/forms/OBSBasicVCamConfig.ui create mode 100644 UI/window-basic-vcam-config.cpp create mode 100644 UI/window-basic-vcam-config.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 8e25b0d6c..1585a49d2 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -103,6 +103,7 @@ target_sources( forms/OBSBasicSettings.ui forms/OBSBasicSourceSelect.ui forms/OBSBasicTransform.ui + forms/OBSBasicVCamConfig.ui forms/OBSExtraBrowsers.ui forms/OBSImporter.ui forms/OBSLogReply.ui @@ -257,6 +258,8 @@ target_sources( window-basic-transform.cpp window-basic-transform.hpp window-basic-preview.hpp + window-basic-vcam-config.cpp + window-basic-vcam-config.hpp window-dock.cpp window-dock.hpp window-importer.cpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 3403d966c..98e8755dc 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -700,6 +700,17 @@ Basic.Main.Ungroup="Ungroup" Basic.Main.GridMode="Grid Mode" Basic.Main.ListMode="List Mode" +# virtual camera configuration +Basic.Main.VirtualCamConfig="Configure Virtual Camera" +Basic.VCam.VirtualCamera="Virtual Camera" +Basic.VCam.OutputType="Output Type" +Basic.VCam.OutputSelection="Output Selection" +Basic.VCam.Internal="Internal" +Basic.VCam.InternalDefault="Program Output (Default)" +Basic.VCam.InternalPreview="Preview Output" +Basic.VCam.Start="Start" +Basic.VCam.Update="Update" + # basic mode file menu Basic.MainMenu.File="&File" Basic.MainMenu.File.Export="&Export" diff --git a/UI/forms/OBSBasicVCamConfig.ui b/UI/forms/OBSBasicVCamConfig.ui new file mode 100644 index 000000000..bc255b159 --- /dev/null +++ b/UI/forms/OBSBasicVCamConfig.ui @@ -0,0 +1,113 @@ + + + OBSBasicVCamConfig + + + + 0 + 0 + 400 + 170 + + + + Basic.VCam.VirtualCamera + + + + + + Basic.VCam.OutputType + + + + + + + + Basic.VCam.Internal + + + + + Basic.Scene + + + + + Basic.Main.Source + + + + + + + + Basic.VCam.OutputSelection + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + OBSBasicVCamConfig + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OBSBasicVCamConfig + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/UI/record-button.cpp b/UI/record-button.cpp index 0e080a796..a0f028938 100644 --- a/UI/record-button.cpp +++ b/UI/record-button.cpp @@ -18,19 +18,143 @@ void RecordButton::resizeEvent(QResizeEvent *event) event->accept(); } -void ReplayBufferButton::resizeEvent(QResizeEvent *event) +static QWidget *firstWidget(QLayoutItem *item) +{ + auto widget = item->widget(); + if (widget) + return widget; + + auto layout = item->layout(); + if (!layout) + return nullptr; + + auto n = layout->count(); + for (auto i = 0, n = layout->count(); i < n; i++) { + widget = firstWidget(layout->itemAt(i)); + if (widget) + return widget; + } + return nullptr; +} + +static QWidget *lastWidget(QLayoutItem *item) +{ + auto widget = item->widget(); + if (widget) + return widget; + + auto layout = item->layout(); + if (!layout) + return nullptr; + + auto n = layout->count(); + for (auto i = layout->count(); i > 0; i--) { + widget = lastWidget(layout->itemAt(i - 1)); + if (widget) + return widget; + } + return nullptr; +} + +static QWidget *getNextWidget(QBoxLayout *container, QLayoutItem *item) +{ + for (auto i = 1, n = container->count(); i < n; i++) { + if (container->itemAt(i - 1) == item) + return firstWidget(container->itemAt(i)); + } + return nullptr; +} + +ControlsSplitButton::ControlsSplitButton(const QString &text, + const QVariant &themeID, + void (OBSBasic::*clicked)()) + : QHBoxLayout(OBSBasic::Get()) +{ + button.reset(new QPushButton(text)); + button->setCheckable(true); + button->setProperty("themeID", themeID); + + button->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + button->installEventFilter(this); + + OBSBasic *main = OBSBasic::Get(); + connect(button.data(), &QPushButton::clicked, main, clicked); + + addWidget(button.data()); +} + +void ControlsSplitButton::addIcon(const QString &name, const QVariant &themeID, + void (OBSBasic::*clicked)()) +{ + icon.reset(new QPushButton()); + icon->setAccessibleName(name); + icon->setToolTip(name); + icon->setChecked(false); + icon->setProperty("themeID", themeID); + + QSizePolicy sp; + sp.setHeightForWidth(true); + icon->setSizePolicy(sp); + + OBSBasic *main = OBSBasic::Get(); + connect(icon.data(), &QAbstractButton::clicked, main, clicked); + + addWidget(icon.data()); + QWidget::setTabOrder(button.data(), icon.data()); + + auto next = getNextWidget(main->ui->buttonsVLayout, this); + if (next) + QWidget::setTabOrder(icon.data(), next); +} + +void ControlsSplitButton::removeIcon() +{ + icon.reset(); +} + +void ControlsSplitButton::insert(int index) { OBSBasic *main = OBSBasic::Get(); - if (!main->replay) - return; + auto count = main->ui->buttonsVLayout->count(); + if (index < 0) + index = 0; + else if (index > count) + index = count; - QSize replaySize = main->replay->size(); - int height = main->ui->recordButton->size().height(); + main->ui->buttonsVLayout->insertLayout(index, this); - if (replaySize.height() != height || replaySize.width() != height) { - main->replay->setMinimumSize(height, height); - main->replay->setMaximumSize(height, height); + QWidget *prev = button.data(); + + if (index > 0) { + prev = lastWidget(main->ui->buttonsVLayout->itemAt(index - 1)); + if (prev) + QWidget::setTabOrder(prev, button.data()); + prev = button.data(); } - event->accept(); + if (icon) { + QWidget::setTabOrder(button.data(), icon.data()); + prev = icon.data(); + } + + if (index < count) { + auto next = firstWidget( + main->ui->buttonsVLayout->itemAt(index + 1)); + if (next) + QWidget::setTabOrder(prev, next); + } +} + +bool ControlsSplitButton::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::Resize && icon) { + QSize iconSize = icon->size(); + int height = button->height(); + + if (iconSize.height() != height || iconSize.width() != height) { + icon->setMinimumSize(height, height); + icon->setMaximumSize(height, height); + } + } + return QObject::eventFilter(obj, event); } diff --git a/UI/record-button.hpp b/UI/record-button.hpp index d6c083e21..ea8d40c7d 100644 --- a/UI/record-button.hpp +++ b/UI/record-button.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include class RecordButton : public QPushButton { Q_OBJECT @@ -11,15 +13,27 @@ public: virtual void resizeEvent(QResizeEvent *event) override; }; -class ReplayBufferButton : public QPushButton { +class OBSBasic; + +class ControlsSplitButton : public QHBoxLayout { Q_OBJECT public: - inline ReplayBufferButton(const QString &text, - QWidget *parent = nullptr) - : QPushButton(text, parent) - { - } + ControlsSplitButton(const QString &text, const QVariant &themeID, + void (OBSBasic::*clicked)()); - virtual void resizeEvent(QResizeEvent *event) override; + void addIcon(const QString &name, const QVariant &themeID, + void (OBSBasic::*clicked)()); + void removeIcon(); + void insert(int index); + + inline QPushButton *first() { return button.data(); } + inline QPushButton *second() { return icon.data(); } + +protected: + virtual bool eventFilter(QObject *obj, QEvent *event) override; + +private: + QScopedPointer button; + QScopedPointer icon; }; diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index dac421632..e423e176f 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -5,6 +5,7 @@ #include "audio-encoders.hpp" #include "window-basic-main.hpp" #include "window-basic-main-outputs.hpp" +#include "window-basic-vcam-config.hpp" using namespace std; @@ -178,6 +179,9 @@ static void OBSStopVirtualCam(void *data, calldata_t *params) os_atomic_set_bool(&virtualcam_active, false); QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", Q_ARG(int, code)); + + obs_output_set_media(output->virtualCam, nullptr, nullptr); + OBSBasicVCamConfig::StopVideo(); } /* ------------------------------------------------------------------------ */ @@ -226,8 +230,11 @@ inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) bool BasicOutputHandler::StartVirtualCam() { if (main->vcamEnabled) { - obs_output_set_media(virtualCam, obs_get_video(), - obs_get_audio()); + video_t *video = OBSBasicVCamConfig::StartVideo(); + if (!video) + return false; + + obs_output_set_media(virtualCam, video, obs_get_audio()); if (!Active()) SetupOutputs(); diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index 8a8d709fe..0c86dd272 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -21,6 +21,7 @@ #include #include #include "window-basic-main.hpp" +#include "window-basic-vcam-config.hpp" #include "display-helpers.hpp" #include "window-namedialog.hpp" #include "menu-button.hpp" @@ -283,6 +284,9 @@ void OBSBasic::OverrideTransition(OBSSource transition) obs_transition_swap_begin(transition, oldTransition); obs_set_output_source(0, transition); obs_transition_swap_end(transition, oldTransition); + + // Transition overrides don't raise an event so we need to call update directly + OBSBasicVCamConfig::UpdateOutputSource(); } } diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 7736e03bf..a244b5039 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -52,6 +52,7 @@ #include "window-basic-main.hpp" #include "window-basic-stats.hpp" #include "window-basic-main-outputs.hpp" +#include "window-basic-vcam-config.hpp" #include "window-log-reply.hpp" #include "window-projector.hpp" #include "window-remux.hpp" @@ -1622,16 +1623,15 @@ void OBSBasic::ReplayBufferClicked() void OBSBasic::AddVCamButton() { - vcamButton = new ReplayBufferButton(QTStr("Basic.Main.StartVirtualCam"), - this); - vcamButton->setCheckable(true); - connect(vcamButton.data(), &QPushButton::clicked, this, - &OBSBasic::VCamButtonClicked); + OBSBasicVCamConfig::Init(); - vcamButton->setProperty("themeID", "vcamButton"); - ui->buttonsVLayout->insertWidget(2, vcamButton); - setTabOrder(ui->recordButton, vcamButton); - setTabOrder(vcamButton, ui->modeSwitch); + vcamButton = new ControlsSplitButton( + QTStr("Basic.Main.StartVirtualCam"), "vcamButton", + &OBSBasic::VCamButtonClicked); + vcamButton->addIcon(QTStr("Basic.Main.VirtualCamConfig"), + QStringLiteral("configIconSmall"), + &OBSBasic::VCamConfigButtonClicked); + vcamButton->insert(2); } void OBSBasic::ResetOutputs() @@ -1647,28 +1647,13 @@ void OBSBasic::ResetOutputs() : CreateSimpleOutputHandler(this)); delete replayBufferButton; - delete replayLayout; if (outputHandler->replayBuffer) { - replayBufferButton = new ReplayBufferButton( - QTStr("Basic.Main.StartReplayBuffer"), this); - replayBufferButton->setCheckable(true); - connect(replayBufferButton.data(), - &QPushButton::clicked, this, + replayBufferButton = new ControlsSplitButton( + QTStr("Basic.Main.StartReplayBuffer"), + "replayBufferButton", &OBSBasic::ReplayBufferClicked); - - replayBufferButton->setSizePolicy(QSizePolicy::Ignored, - QSizePolicy::Fixed); - - replayLayout = new QHBoxLayout(this); - replayLayout->addWidget(replayBufferButton); - - replayBufferButton->setProperty("themeID", - "replayBufferButton"); - ui->buttonsVLayout->insertLayout(2, replayLayout); - setTabOrder(ui->recordButton, replayBufferButton); - setTabOrder(replayBufferButton, - ui->buttonsVLayout->itemAt(3)->widget()); + replayBufferButton->insert(2); } if (sysTrayReplayBuffer) @@ -7257,19 +7242,19 @@ void OBSBasic::StartReplayBuffer() return; if (!UIValidation::NoSourcesConfirmation(this)) { - replayBufferButton->setChecked(false); + replayBufferButton->first()->setChecked(false); return; } if (!OutputPathValid()) { OutputPathInvalidMessage(); - replayBufferButton->setChecked(false); + replayBufferButton->first()->setChecked(false); return; } if (LowDiskSpace()) { DiskSpaceMessage(); - replayBufferButton->setChecked(false); + replayBufferButton->first()->setChecked(false); return; } @@ -7279,7 +7264,7 @@ void OBSBasic::StartReplayBuffer() SaveProject(); if (!outputHandler->StartReplayBuffer()) { - replayBufferButton->setChecked(false); + replayBufferButton->first()->setChecked(false); } else if (os_atomic_load_bool(&recording_paused)) { ShowReplayBufferPauseWarning(); } @@ -7290,10 +7275,12 @@ void OBSBasic::ReplayBufferStopping() if (!outputHandler || !outputHandler->replayBuffer) return; - replayBufferButton->setText(QTStr("Basic.Main.StoppingReplayBuffer")); + replayBufferButton->first()->setText( + QTStr("Basic.Main.StoppingReplayBuffer")); if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(replayBufferButton->text()); + sysTrayReplayBuffer->setText( + replayBufferButton->first()->text()); replayBufferStopping = true; if (api) @@ -7318,11 +7305,13 @@ void OBSBasic::ReplayBufferStart() if (!outputHandler || !outputHandler->replayBuffer) return; - replayBufferButton->setText(QTStr("Basic.Main.StopReplayBuffer")); - replayBufferButton->setChecked(true); + replayBufferButton->first()->setText( + QTStr("Basic.Main.StopReplayBuffer")); + replayBufferButton->first()->setChecked(true); if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(replayBufferButton->text()); + sysTrayReplayBuffer->setText( + replayBufferButton->first()->text()); replayBufferStopping = false; if (api) @@ -7375,11 +7364,13 @@ void OBSBasic::ReplayBufferStop(int code) if (!outputHandler || !outputHandler->replayBuffer) return; - replayBufferButton->setText(QTStr("Basic.Main.StartReplayBuffer")); - replayBufferButton->setChecked(false); + replayBufferButton->first()->setText( + QTStr("Basic.Main.StartReplayBuffer")); + replayBufferButton->first()->setChecked(false); if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(replayBufferButton->text()); + sysTrayReplayBuffer->setText( + replayBufferButton->first()->text()); blog(LOG_INFO, REPLAY_BUFFER_STOP); @@ -7428,7 +7419,7 @@ void OBSBasic::StartVirtualCam() SaveProject(); if (!outputHandler->StartVirtualCam()) { - vcamButton->setChecked(false); + vcamButton->first()->setChecked(false); } } @@ -7450,10 +7441,10 @@ void OBSBasic::OnVirtualCamStart() if (!outputHandler || !outputHandler->virtualCam) return; - vcamButton->setText(QTStr("Basic.Main.StopVirtualCam")); + vcamButton->first()->setText(QTStr("Basic.Main.StopVirtualCam")); if (sysTrayVirtualCam) sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - vcamButton->setChecked(true); + vcamButton->first()->setChecked(true); if (api) api->on_event(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); @@ -7468,10 +7459,10 @@ void OBSBasic::OnVirtualCamStop(int) if (!outputHandler || !outputHandler->virtualCam) return; - vcamButton->setText(QTStr("Basic.Main.StartVirtualCam")); + vcamButton->first()->setText(QTStr("Basic.Main.StartVirtualCam")); if (sysTrayVirtualCam) sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - vcamButton->setChecked(false); + vcamButton->first()->setChecked(false); if (api) api->on_event(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); @@ -7623,7 +7614,7 @@ void OBSBasic::VCamButtonClicked() StopVirtualCam(); } else { if (!UIValidation::NoSourcesConfirmation(this)) { - vcamButton->setChecked(false); + vcamButton->first()->setChecked(false); return; } @@ -7631,6 +7622,12 @@ void OBSBasic::VCamButtonClicked() } } +void OBSBasic::VCamConfigButtonClicked() +{ + OBSBasicVCamConfig config(this); + config.exec(); +} + void OBSBasic::on_settingsButton_clicked() { on_action_Settings_triggered(); @@ -9757,6 +9754,7 @@ void OBSBasic::PauseRecording() os_atomic_set_bool(&recording_paused, true); + auto replay = replayBufferButton->second(); if (replay) replay->setEnabled(false); @@ -9801,6 +9799,7 @@ void OBSBasic::UnpauseRecording() os_atomic_set_bool(&recording_paused, false); + auto replay = replayBufferButton->second(); if (replay) replay->setEnabled(true); @@ -9877,28 +9876,13 @@ void OBSBasic::UpdateReplayBuffer(bool activate) { if (!activate || !outputHandler || !outputHandler->ReplayBufferActive()) { - replay.reset(); + replayBufferButton->removeIcon(); return; } - replay.reset(new QPushButton()); - replay->setAccessibleName(QTStr("Basic.Main.SaveReplay")); - replay->setToolTip(QTStr("Basic.Main.SaveReplay")); - replay->setChecked(false); - replay->setProperty("themeID", - QVariant(QStringLiteral("replayIconSmall"))); - - QSizePolicy sp; - sp.setHeightForWidth(true); - replay->setSizePolicy(sp); - - connect(replay.data(), &QAbstractButton::clicked, this, - &OBSBasic::ReplayBufferSave); - replayLayout->addWidget(replay.data()); - setTabOrder(replayLayout->itemAt(0)->widget(), - replayLayout->itemAt(1)->widget()); - setTabOrder(replayLayout->itemAt(1)->widget(), - ui->buttonsVLayout->itemAt(3)->widget()); + replayBufferButton->addIcon(QTStr("Basic.Main.SaveReplay"), + QStringLiteral("replayIconSmall"), + &OBSBasic::ReplayBufferSave); } #define MBYTE (1024ULL * 1024ULL) diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index d16dad7cd..ff9c6ac9b 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -178,7 +178,7 @@ class OBSBasic : public OBSMainWindow { friend class AutoConfig; friend class AutoConfigStreamPage; friend class RecordButton; - friend class ReplayBufferButton; + friend class ControlsSplitButton; friend class ExtraBrowsersModel; friend class ExtraBrowsersDelegate; friend class DeviceCaptureToolbar; @@ -298,12 +298,10 @@ private: QPointer startStreamMenu; QPointer transitionButton; - QPointer replayBufferButton; - QPointer replayLayout; + QPointer replayBufferButton; QScopedPointer pause; - QScopedPointer replay; - QPointer vcamButton; + QPointer vcamButton; bool vcamEnabled = false; QScopedPointer trayIcon; @@ -1038,6 +1036,7 @@ private slots: void on_streamButton_clicked(); void on_recordButton_clicked(); void VCamButtonClicked(); + void VCamConfigButtonClicked(); void on_settingsButton_clicked(); void Screenshot(OBSSource source_ = nullptr); void ScreenshotSelectedSource(); diff --git a/UI/window-basic-vcam-config.cpp b/UI/window-basic-vcam-config.cpp new file mode 100644 index 000000000..5f21f71e7 --- /dev/null +++ b/UI/window-basic-vcam-config.cpp @@ -0,0 +1,264 @@ +#include "window-basic-vcam-config.hpp" +#include "window-basic-main.hpp" +#include "qt-wrappers.hpp" +#include "remote-text.hpp" +#include +#include +#include +#include + +using namespace std; + +enum class VCamOutputType { + Internal, + Scene, + Source, +}; + +enum class VCamInternalType { + Default, + Preview, +}; + +struct VCamConfig { + VCamOutputType type = VCamOutputType::Internal; + VCamInternalType internal = VCamInternalType::Default; + string scene; + string source; +}; + +static VCamConfig *vCamConfig = nullptr; + +OBSBasicVCamConfig::OBSBasicVCamConfig(QWidget *parent) + : QDialog(parent), ui(new Ui::OBSBasicVCamConfig) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + ui->setupUi(this); + + auto type = (int)vCamConfig->type; + ui->outputType->setCurrentIndex(type); + OutputTypeChanged(type); + connect(ui->outputType, + static_cast( + &QComboBox::currentIndexChanged), + this, &OBSBasicVCamConfig::OutputTypeChanged); + + auto start = ui->buttonBox->button(QDialogButtonBox::Ok); + if (!obs_frontend_virtualcam_active()) + start->setText(QTStr("Basic.VCam.Start")); + else + start->setText(QTStr("Basic.VCam.Update")); + connect(start, &QPushButton::clicked, this, + &OBSBasicVCamConfig::SaveAndStart); +} + +void OBSBasicVCamConfig::OutputTypeChanged(int type) +{ + auto list = ui->outputSelection; + list->clear(); + + switch ((VCamOutputType)type) { + case VCamOutputType::Internal: + list->addItem(QTStr("Basic.VCam.InternalDefault")); + list->addItem(QTStr("Basic.VCam.InternalPreview")); + list->setCurrentIndex((int)vCamConfig->internal); + break; + + case VCamOutputType::Scene: { + // Scenes in default order + BPtr scenes = obs_frontend_get_scene_names(); + int idx = 0; + for (char **temp = scenes; *temp; temp++) { + list->addItem(*temp); + + if (vCamConfig->scene.compare(*temp) == 0) + list->setCurrentIndex(list->count() - 1); + } + break; + } + + case VCamOutputType::Source: { + // Sources in alphabetical order + vector sources; + auto AddSource = [&](obs_source_t *source) { + auto name = obs_source_get_name(source); + auto flags = obs_source_get_output_flags(source); + + if (!(obs_source_get_output_flags(source) & + OBS_SOURCE_VIDEO)) + return; + + sources.push_back(name); + }; + using AddSource_t = decltype(AddSource); + + obs_enum_sources( + [](void *data, obs_source_t *source) { + auto &AddSource = + *static_cast(data); + if (!obs_source_removed(source)) + AddSource(source); + return true; + }, + static_cast(&AddSource)); + + // Sort and select current item + sort(sources.begin(), sources.end()); + for (auto &&source : sources) { + list->addItem(source.c_str()); + + if (vCamConfig->source == source) + list->setCurrentIndex(list->count() - 1); + } + break; + } + } +} + +void OBSBasicVCamConfig::SaveAndStart() +{ + auto type = (VCamOutputType)ui->outputType->currentIndex(); + auto out = ui->outputSelection; + switch (type) { + case VCamOutputType::Internal: + vCamConfig->internal = (VCamInternalType)out->currentIndex(); + break; + case VCamOutputType::Scene: + vCamConfig->scene = out->currentText().toStdString(); + break; + case VCamOutputType::Source: + vCamConfig->source = out->currentText().toStdString(); + break; + default: + // unknown value, don't save type + return; + } + + vCamConfig->type = type; + + // Start the vcam if needed, if already running just update the source + if (!obs_frontend_virtualcam_active()) + obs_frontend_start_virtualcam(); + else + UpdateOutputSource(); +} + +static void SaveCallback(obs_data_t *data, bool saving, void *) +{ + if (saving) { + OBSDataAutoRelease obj = obs_data_create(); + + obs_data_set_int(obj, "type", (int)vCamConfig->type); + obs_data_set_int(obj, "internal", (int)vCamConfig->internal); + obs_data_set_string(obj, "scene", vCamConfig->scene.c_str()); + obs_data_set_string(obj, "source", vCamConfig->source.c_str()); + + obs_data_set_obj(data, "virtual-camera", obj); + } else { + OBSDataAutoRelease obj = + obs_data_get_obj(data, "virtual-camera"); + + vCamConfig->type = + (VCamOutputType)obs_data_get_int(obj, "type"); + vCamConfig->internal = + (VCamInternalType)obs_data_get_int(obj, "internal"); + vCamConfig->scene = obs_data_get_string(obj, "scene"); + vCamConfig->source = obs_data_get_string(obj, "source"); + } +} + +static void EventCallback(enum obs_frontend_event event, void *) +{ + if (vCamConfig->type != VCamOutputType::Internal) + return; + + // Update output source if the preview scene changes + // or if the default transition is changed + switch (event) { + case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: + if (vCamConfig->internal != VCamInternalType::Preview) + return; + break; + case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: + if (vCamConfig->internal != VCamInternalType::Default) + return; + break; + default: + return; + } + + OBSBasicVCamConfig::UpdateOutputSource(); +} + +void OBSBasicVCamConfig::Init() +{ + if (vCamConfig) + return; + + vCamConfig = new VCamConfig; + + obs_frontend_add_save_callback(SaveCallback, nullptr); + obs_frontend_add_event_callback(EventCallback, nullptr); +} + +static obs_view_t *view = nullptr; +static video_t *video = nullptr; + +video_t *OBSBasicVCamConfig::StartVideo() +{ + if (!video) { + view = obs_view_create(); + video = obs_view_add(view); + } + UpdateOutputSource(); + return video; +} + +void OBSBasicVCamConfig::StopVideo() +{ + if (view) { + obs_view_remove(view); + obs_view_set_source(view, 0, nullptr); + obs_view_destroy(view); + view = nullptr; + } + video = nullptr; +} + +void OBSBasicVCamConfig::UpdateOutputSource() +{ + if (!view) + return; + + obs_source_t *source = nullptr; + + switch ((VCamOutputType)vCamConfig->type) { + case VCamOutputType::Internal: + switch (vCamConfig->internal) { + case VCamInternalType::Default: + source = obs_get_output_source(0); + break; + case VCamInternalType::Preview: + OBSSource s = OBSBasic::Get()->GetCurrentSceneSource(); + obs_source_get_ref(s); + source = s; + break; + } + break; + + case VCamOutputType::Scene: + source = obs_get_source_by_name(vCamConfig->scene.c_str()); + break; + + case VCamOutputType::Source: + source = obs_get_source_by_name(vCamConfig->source.c_str()); + break; + } + + auto current = obs_view_get_source(view, 0); + if (source != current) + obs_view_set_source(view, 0, source); + obs_source_release(source); + obs_source_release(current); +} diff --git a/UI/window-basic-vcam-config.hpp b/UI/window-basic-vcam-config.hpp new file mode 100644 index 000000000..4a00e0219 --- /dev/null +++ b/UI/window-basic-vcam-config.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#include "ui_OBSBasicVCamConfig.h" + +class OBSBasicVCamConfig : public QDialog { + Q_OBJECT + +public: + static void Init(); + + static video_t *StartVideo(); + static void StopVideo(); + static void UpdateOutputSource(); + + explicit OBSBasicVCamConfig(QWidget *parent = 0); + +private slots: + void OutputTypeChanged(int type); + void SaveAndStart(); + +private: + std::unique_ptr ui; +};